Merge pull request #1635 from BetterDiscord/modals

Reduce reliance on internals and improve UI consistency
This commit is contained in:
Zerebos 2023-08-28 22:33:36 -04:00 committed by GitHub
commit 9f950c10f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1908 additions and 396 deletions

View File

@ -1,51 +0,0 @@
import ClassName from "@structs/classname";
import Utilities from "./utilities";
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

@ -12,151 +12,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

@ -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

@ -3,9 +3,9 @@ import Utilities from "@modules/utilities";
export default class SimpleMarkdownExt {
static parseToReact(str) {
static parseToReact(str, inline = true) {
if (!this._parser) this._initialize();
return this._renderer(this._parse(str, {inline: true}));
return this._renderer(this._parse(str, {inline}));
}
static _initialize() {

View File

@ -1,4 +1,4 @@
.bd-button {
/* .bd-button {
display: inline-flex;
justify-content: center;
align-items: center;
@ -59,4 +59,539 @@
.bd-button-disabled:hover {
cursor: not-allowed;
}
} */
/* Generic Button Styles */
.bd-button {
position: relative;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
background: none;
border: none;
border-radius: 3px;
font-size: 14px;
font-weight: 500;
line-height: 16px;
padding: 2px 16px;
user-select: none;
}
.bd-button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.bd-button .bd-button-content {
--button--underline-color: transparent;
background-image: linear-gradient(0deg, transparent, transparent 1px, var(--button--underline-color) 0, var(--button--underline-color) 2px, transparent 0);
}
.bd-button:disabled .bd-button-content {
background-image: none !important;
}
.bd-button-outlined:disabled {
background-color: transparent !important;
}
/* Button Sizes */
.bd-button-tiny {
width: 52px;
height: 24px;
min-width: 52px;
min-height: 24px;
}
.bd-button-small {
width: 60px;
height: 32px;
min-width: 60px;
min-height: 32px;
}
.bd-button-medium {
width: 96px;
height: 38px;
min-width: 96px;
min-height: 38px;
}
.bd-button-large {
width: 130px;
height: 44px;
min-width: 130px;
min-height: 44px;
}
.bd-button-xlarge {
width: 148px;
height: 50px;
min-width: 148px;
min-height: 50px;
font-size: 16px;
line-height: normal;
padding: 2px 20px;
}
.bd-button-icon {
height: auto;
padding: 4px;
}
.bd-button-grow,
.bd-button-icon {
width: auto
}
/* Button Looks */
.bd-button-filled {
-webkit-transition: background-color .17s ease, color .17s ease;
transition: background-color .17s ease, color .17s ease
}
.bd-button-outlined {
-webkit-transition: color .17s ease, background-color .17s ease, border-color .17s ease;
transition: color .17s ease, background-color .17s ease, border-color .17s ease;
border-width: 1px;
border-style: solid
}
.bd-button-blank {
background: transparent;
color: currentColor;
border: 0;
padding: 0;
margin: 0
}
.bd-button-filled .bd-button-content,
.bd-button-link .bd-button-content,
.bd-button-outlined .bd-button-content {
margin: 0 auto;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
/* COLORS */
/* Color BD Brand */
.bd-button-filled.bd-button-color-brand {
color: var(--white-500);
background-color: #3E82E5; /* BD Blue */
}
.bd-button-filled.bd-button-color-brand:hover {
background-color: #3875CE;
}
.bd-button-filled.bd-button-color-brand:active {
background-color: #3268B7;
}
.bd-button-filled.bd-button-color-brand:disabled {
background-color: #3E82E5; /* BD Blue */
opacity: 0.4;
}
.bd-button-outlined.bd-button-color-brand {
color: var(--button-outline-brand-text);
border-color: var(--button-outline-brand-border);
}
.bd-button-outlined.bd-button-color-brand:hover {
background-color: var(--button-outline-brand-background-hover);
border-color: var(--button-outline-brand-border-hover);
color: var(--button-outline-brand-text-hover);
}
.bd-button-outlined.bd-button-color-brand:active {
background-color: var(--button-outline-brand-background-active);
border-color: var(--button-outline-brand-border-active);
color: var(--button-outline-brand-text-active);
}
.bd-button-link.bd-button-color-brand {
color: #3E82E5; /* BD Blue */
}
.bd-button-link.bd-button-color-brand:hover .bd-button-content {
--button--underline-color: #3E82E5; /* BD Blue */
}
/* Color Blurple */
.bd-button-filled.bd-button-color-blurple {
color: var(--white-500);
background-color: var(--brand-experiment)
}
.bd-button-filled.bd-button-color-blurple:hover {
background-color: var(--brand-experiment-560)
}
.bd-button-filled.bd-button-color-blurple:active {
background-color: var(--brand-experiment-600)
}
.bd-button-filled.bd-button-color-blurple:disabled {
background-color: var(--brand-experiment)
}
.bd-button-outlined.bd-button-color-blurple {
color: var(--button-outline-brand-text);
border-color: var(--button-outline-brand-border)
}
.bd-button-outlined.bd-button-color-blurple:hover {
background-color: var(--button-outline-brand-background-hover);
border-color: var(--button-outline-brand-border-hover);
color: var(--button-outline-brand-text-hover)
}
.bd-button-outlined.bd-button-color-blurple:active {
background-color: var(--button-outline-brand-background-active);
border-color: var(--button-outline-brand-border-active);
color: var(--button-outline-brand-text-active)
}
.bd-button-link.bd-button-color-blurple {
color: var(--brand-experiment)
}
.bd-button-link.bd-button-color-blurple:hover .bd-button-content {
--button--underline-color: var(--brand-experiment)
}
/* Color Yellow/Warn */
.bd-button-filled.bd-button-color-yellow {
color: var(--white-500);
background-color: var(--status-warning)
}
.bd-button-filled.bd-button-color-yellow:active,
.bd-button-filled.bd-button-color-yellow:hover {
background-color: null;
}
.bd-button-filled.bd-button-color-yellow:disabled {
background-color: var(--status-warning)
}
.bd-button-outlined.bd-button-color-yellow {
color: var(--status-warning);
border-color: var(--status-warning)
}
.bd-button-outlined.bd-button-color-yellow:active {
background-color: hsl(var(--yellow-300-hsl)/.1)
}
.bd-button-link.bd-button-color-yellow {
color: var(--status-warning)
}
.bd-button-link.bd-button-color-yellow:hover .bd-button-content {
--button--underline-color: var(--status-warning)
}
/* Color Link */
.bd-button-filled.bd-button-color-link {
color: var(--white-500);
background-color: var(--text-link)
}
.bd-button-filled.bd-button-color-link:active,
.bd-button-filled.bd-button-color-link:hover {
background-color: null
}
.bd-button-filled.bd-button-color-link:disabled {
background-color: var(--text-link)
}
.bd-button-outlined.bd-button-color-link {
color: var(--text-link);
border-color: var(--text-link)
}
.bd-button-outlined.bd-button-color-link:active {
background-color: hsl(var(--text-link-hsl)/.1)
}
.bd-button-link.bd-button-color-link {
color: var(--text-link)
}
.bd-button-link.bd-button-color-link:hover .bd-button-content {
--button--underline-color: var(--text-link)
}
/* Color White */
.bd-button-filled.bd-button-color-white {
color: var(--primary-500);
background-color: var(--white-500)
}
.bd-button-filled.bd-button-color-white:active,
.bd-button-filled.bd-button-color-white:hover {
background-color: null
}
.bd-button-filled.bd-button-color-white:disabled {
background-color: var(--white-500)
}
.bd-button-outlined.bd-button-color-white {
color: var(--white-500);
border-color: var(--white-500)
}
.bd-button-outlined.bd-button-color-white:active {
background-color: hsl(var(--white-500-hsl)/.1)
}
.bd-button-link.bd-button-color-white {
color: var(--white-500)
}
.bd-button-link.bd-button-color-white:hover .bd-button-content {
--button--underline-color: var(--white-500)
}
/* Color Red/Danger/Error */
.bd-button-filled.bd-button-color-red {
color: var(--white-500);
background-color: var(--button-danger-background)
}
.bd-button-filled.bd-button-color-red:hover {
background-color: var(--button-danger-background-hover)
}
.bd-button-filled.bd-button-color-red:active {
background-color: var(--button-danger-background-active)
}
.bd-button-filled.bd-button-color-red:disabled {
background-color: var(--button-danger-background-disabled)
}
.bd-button-outlined.bd-button-color-red {
color: var(--button-outline-danger-text);
border-color: var(--button-outline-danger-border)
}
.bd-button-outlined.bd-button-color-red:hover {
background-color: var(--button-outline-danger-background-hover);
border-color: var(--button-outline-danger-border-hover);
color: var(--button-outline-danger-text-hover)
}
.bd-button-outlined.bd-button-color-red:active {
background-color: var(--button-outline-danger-background-active);
border-color: var(--button-outline-danger-border-active);
color: var(--button-outline-danger-text-active)
}
.bd-button-link.bd-button-color-red {
color: var(--text-danger)
}
.bd-button-link.bd-button-color-red:hover .bd-button-content {
--button--underline-color: var(--text-danger)
}
/* Color Green Success */
.bd-button-filled.bd-button-color-green {
color: var(--white-500);
background-color: var(--button-positive-background)
}
.bd-button-filled.bd-button-color-green:hover {
background-color: var(--button-positive-background-hover)
}
.bd-button-filled.bd-button-color-green:active {
background-color: var(--button-positive-background-active)
}
.bd-button-filled.bd-button-color-green:disabled {
background-color: var(--button-positive-background-disabled)
}
.bd-button-outlined.bd-button-color-green {
color: var(--button-outline-positive-text);
border-color: var(--button-outline-positive-border)
}
.bd-button-outlined.bd-button-color-green:hover {
background-color: var(--button-outline-positive-background-hover);
border-color: var(--button-outline-positive-border-hover);
color: var(--button-outline-positive-text-hover)
}
.bd-button-outlined.bd-button-color-green:active {
background-color: var(--button-outline-positive-background-active);
border-color: var(--button-outline-positive-border-active);
color: var(--button-outline-positive-text-active)
}
.bd-button-link.bd-button-color-green {
color: var(--green-360)
}
.bd-button-link.bd-button-color-green:hover .bd-button-content {
--button--underline-color: var(--green-360)
}
/* Color Primary/Grey */
.bd-button-outlined.bd-button-color-primary {
color: var(--button-outline-primary-text);
border-color: var(--button-outline-primary-border)
}
.bd-button-outlined.bd-button-color-primary:hover {
background-color: var(--button-outline-primary-background-hover);
border-color: var(--button-outline-primary-border-hover);
color: var(--button-outline-primary-text-hover)
}
.bd-button-outlined.bd-button-color-primary:active {
background-color: var(--button-outline-primary-background-active);
border-color: var(--button-outline-primary-border-active);
color: var(--button-outline-primary-text-active)
}
.bd-button-filled.bd-button-color-primary {
color: var(--white-500);
background-color: var(--button-secondary-background)
}
.bd-button-filled.bd-button-color-primary:hover {
background-color: var(--button-secondary-background-hover)
}
.bd-button-filled.bd-button-color-primary:active {
background-color: var(--button-secondary-background-active)
}
.bd-button-filled.bd-button-color-primary:disabled {
background-color: var(--button-secondary-background-disabled)
}
.theme-dark .bd-button-link.bd-button-color-primary {
color: var(--white-500)
}
.theme-dark .bd-button-link.bd-button-color-primary:hover .bd-button-content {
--button--underline-color: var(--white-500)
}
.theme-light .bd-button-link.bd-button-color-primary {
color: var(--primary-400)
}
.theme-light .bd-button-link.bd-button-color-primary:hover .bd-button-content {
--button--underline-color: var(--primary-400)
}
/* Color Transparent */
.theme-dark .bd-button-filled.bd-button-color-transparent {
color: var(--primary-100);
background-color: hsl(var(--white-500-hsl)/.1)
}
.theme-dark .bd-button-filled.bd-button-color-transparent:hover {
background-color: hsl(var(--white-500-hsl)/.05)
}
.theme-dark .bd-button-filled.bd-button-color-transparent:active {
background-color: hsl(var(--white-500-hsl)/.01)
}
.theme-dark .bd-button-filled.bd-button-color-transparent:disabled {
background-color: hsl(var(--white-500-hsl)/.1)
}
.theme-dark .bd-button-outlined.bd-button-color-transparent {
color: var(--primary-200);
border-color: var(--primary-200)
}
.theme-dark .bd-button-outlined.bd-button-color-transparent:active {
background-color: hsl(var(--primary-200-hsl)/.1)
}
.theme-dark .bd-button-link.bd-button-color-transparent {
color: var(--primary-200)
}
.theme-dark .bd-button-link.bd-button-color-transparent:hover .bd-button-content {
--button--underline-color: var(--primary-200)
}
.theme-light .bd-button-filled.bd-button-color-transparent {
color: var(--primary-400);
background-color: hsl(var(--primary-400-hsl)/.01)
}
.theme-light .bd-button-filled.bd-button-color-transparent:hover {
background-color: hsl(var(--primary-400-hsl)/.2)
}
.theme-light .bd-button-filled.bd-button-color-transparent:active {
background-color: hsl(var(--primary-400-hsl)/.25)
}
.theme-light .bd-button-filled.bd-button-color-transparent:disabled {
background-color: hsl(var(--primary-400-hsl)/.01)
}
.theme-light .bd-button-outlined.bd-button-color-transparent {
color: var(--primary-400);
border-color: var(--primary-400)
}
.theme-light .bd-button-outlined.bd-button-color-transparent:active {
background-color: hsl(var(--primary-400-hsl)/.1)
}
.theme-light .bd-button-link.bd-button-color-transparent {
color: var(--primary-400)
}
.theme-light .bd-button-link.bd-button-color-transparent:hover .bd-button-content {
--button--underline-color: var(--primary-400)
}

View File

@ -6,6 +6,7 @@
@import "./buttons.css";
@import "./spinner.css";
@import "./search.css";
@import "./text.css";
.bd-chat-badge {
vertical-align: bottom;
@ -19,23 +20,6 @@
margin-left: 4px;
}
.bd-changelog-modal video,
.bd-changelog-modal img {
width: 100%;
border-radius: 5px;
outline: none;
}
.bd-changelog-modal code.inline {
padding: 0.2em;
margin: -0.2em 0;
border-radius: 3px;
font-size: 85%;
line-height: 1.125rem;
white-space: pre-wrap;
background: var(--background-secondary);
}
.bd-link {
text-decoration: none;
}

View File

@ -0,0 +1,86 @@
.bd-text-normal {
color: var(--text-normal);
}
.bd-text-muted {
color: var(--text-muted);
}
.bd-text-error {
color: var(--red-400);
}
.bd-text-brand {
color: var(--text-brand);
}
.bd-text-link {
color: var(--text-link);
}
.bd-header-primary {
color: var(--header-primary);
}
.bd-header-secondary {
color: var(--header-secondary);
}
.bd-text-yellow {
color: var(--text-warning);
}
.bd-text-green {
color: var(--text-positive);
}
.bd-text-red {
color: var(--status-danger);
}
.bd-text-white {
color: var(--white-500);
}
.bd-text-10 {
font-size: 10px;
line-height: 12px;
}
.bd-text-12 {
font-size: 12px;
line-height: 16px;
}
.bd-text-14 {
font-size: 14px;
line-height: 18px;
}
.bd-text-16 {
font-size: 16px;
line-height: 20px;
}
.bd-text-20 {
font-size: 20px;
line-height: 24px;
}
.bd-text-24 {
font-size: 24px;
line-height: 30px;
}
.bd-text-32 {
font-size: 32px;
line-height: 40px;
}
.bd-text-strong {
font-weight: 600;
}
.bd-selectable {
user-select: text;
}

View File

@ -166,6 +166,7 @@
.bd-settings-title {
color: var(--header-primary, #FFFFFF);
display: flex;
font-weight: 600;
cursor: default;
flex: 1;

View File

@ -0,0 +1,217 @@
.bd-changelog-modal video,
.bd-changelog-modal img {
width: 100%;
border-radius: 5px;
outline: none;
}
.bd-changelog-modal code.inline {
padding: 0.2em;
margin: -0.2em 0;
border-radius: 3px;
font-size: 85%;
line-height: 1.125rem;
white-space: pre-wrap;
background: var(--background-secondary);
}
.bd-changelog-modal .bd-modal-content {
font-size: 16px;
line-height: 20px;
padding-bottom: 20px;
}
.bd-changelog-modal .bd-modal-content .emoji {
object-fit: contain;
width: 22px;
height: 22px;
}
.bd-changelog-modal .bd-modal-content h1 {
line-height: 20px;
font-size: 16px;
}
.bd-changelog-modal .bd-modal-content h1,
.bd-changelog-modal .bd-modal-content h2,
.bd-changelog-modal .bd-modal-content strong {
font-weight: 700;
}
.bd-changelog-modal .bd-modal-content em,
.bd-changelog-modal .bd-modal-content i {
font-style: italic;
}
.bd-changelog-modal .bd-modal-content p + p {
margin-top: 10px;
}
.bd-changelog-modal .bd-modal-content ol {
margin: 16px 0 16px 16px;
}
.bd-changelog-modal .bd-modal-content ol li {
list-style-type: decimal;
margin-bottom: 8px;
margin-left: 20px;
}
.bd-changelog-modal .bd-modal-content ul {
margin: 20px 0 8px 20px;
}
.bd-changelog-modal .bd-modal-content ul ul {
margin-top: 8px;
}
.bd-changelog-modal .bd-modal-content ul li {
position: relative;
list-style: none;
margin-bottom: 8px;
user-select: text;
}
.bd-changelog-modal .bd-modal-content ul li:last-child {
margin-bottom: 0;
}
.bd-changelog-modal .bd-modal-content ul li::before {
content: "";
position: absolute;
top: 10px;
left: -15px;
width: 6px;
height: 6px;
margin-top: -4px;
margin-left: -3px;
border-radius: 50%;
opacity: 0.3;
}
.bd-changelog-modal .bd-modal-content ul li li::before {
top: 12px;
height: 2px;
border-radius: 0;
}
.bd-changelog-modal .bd-modal-content img {
width: 100%;
}
.bd-changelog-modal .bd-modal-content a {
color: hsl(200, calc(var(--saturation-factor, 1) * 100%), 49.4%);
transition: 0.05s;
text-decoration: none;
}
.bd-changelog-modal .bd-modal-content a:hover {
text-decoration: underline;
}
.theme-dark .bd-changelog-modal .bd-modal-content ol,
.theme-dark .bd-changelog-modal .bd-modal-content p,
.theme-dark .bd-changelog-modal .bd-modal-content ul li {
color: hsl(210, calc(var(--saturation-factor, 1) * 9.3%), 78.8%);
}
.theme-dark .bd-changelog-modal .bd-modal-content ul li::before {
background-color: hsl(216, calc(var(--saturation-factor, 1) * 9.8%), 90%);
}
.theme-light .bd-changelog-modal .bd-modal-content ol,
.theme-light .bd-changelog-modal .bd-modal-content p,
.theme-light .bd-changelog-modal .bd-modal-content ul li {
color: hsl(223, calc(var(--saturation-factor, 1) * 5.8%), 52.9%);
}
.theme-light .bd-changelog-modal .bd-modal-content ul li::before {
background-color: hsl(223, calc(var(--saturation-factor, 1) * 5.8%), 52.9%);
}
.bd-changelog-title {
font-weight: 700;
font-size: 16px;
line-height: 20px;
text-transform: uppercase;
}
.bd-changelog-title {
display: flex;
align-items: center;
margin-top: 40px;
}
.bd-changelog-title.bd-changelog-first {
margin-top: 20px;
}
.bd-changelog-title::after {
content: "";
height: 1px;
flex: 1 1 auto;
margin-left: 4px;
opacity: .6;
}
.bd-changelog-added {
color: var(--text-positive);
}
.bd-changelog-added::after {
background-color: var(--info-positive-foreground);
}
.bd-changelog-fixed {
color: hsl(359, calc(var(--saturation-factor, 1)*87.3%), 59.8%);
}
.bd-changelog-fixed::after {
background-color: hsl(359, calc(var(--saturation-factor, 1)*87.3%), 59.8%);
}
.bd-changelog-progress {
color: var(--text-warning);
}
.bd-changelog-progress::after {
background-color: var(--info-warning-foreground);
}
.bd-changelog-improved {
color: hsl(235, calc(var(--saturation-factor, 1)*85.6%), 64.7%);
}
.bd-changelog-improved::after {
background-color: hsl(235, calc(var(--saturation-factor, 1)*85.6%), 64.7%);
}
.theme-dark .bd-changelog-improved {
color: hsl(235, calc(var(--saturation-factor, 1)*86.1%), 77.5%);
}
.theme-dark .bd-changelog-improved::after {
background-color: hsl(235, calc(var(--saturation-factor, 1)*86.1%), 77.5%);
}
.theme-dark .bd-changelog-modal video,
.theme-dark .bd-changelog-modal img {
box-shadow: 0 2px 10px 0 hsl(var(0, calc(var(--saturation-factor, 1)*0%), 0%-hsl)/.2);
}
.theme-light .bd-changelog-modal video,
.theme-light .bd-changelog-modal img {
box-shadow: 0 2px 10px 0 hsl(var(0, calc(var(--saturation-factor, 1)*0%), 0%-hsl)/.1);
}
/* .socialLink-1qjJIk {
margin-right: 16px;
}
.theme-light .socialLink-1qjJIk {
color: hsl(228, calc(var(--saturation-factor, 1)*6%), 32.5%);
}
.theme-dark .socialLink-1qjJIk {
color: hsl(210, calc(var(--saturation-factor, 1)*9.3%), 78.8%);
} */

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

@ -0,0 +1,124 @@
.bd-flex {
display: flex;
}
.bd-flex-align-start {
align-items: flex-start;
}
.bd-flex-align-end {
align-items: flex-end;
}
.bd-flex-align-center {
align-items: center;
}
.bd-flex-align-stretch {
align-items: stretch;
}
.bd-flex-align-baseline {
align-items: baseline
}
.bd-flex-justify-start {
justify-content: flex-start;
}
.bd-flex-justify-end {
justify-content: flex-end;
}
.bd-flex-justify-center {
justify-content: center;
}
.bd-flex-justify-around {
justify-content: space-around;
}
.bd-flex-justify-between {
justify-content: space-between;
}
.bd-flex-no-wrap {
flex-wrap: nowrap;
}
.bd-flex-wrap {
flex-wrap: wrap;
}
.bd-flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.bd-flex-horizontal {
flex-direction: row;
}
.bd-flex-reverse {
flex-direction: row-reverse;
}
.bd-flex-vertical {
flex-direction: column;
}
.spacer-2upayl {
flex: 1;
overflow: hidden;
}
.bd-flex-vertical {}
.bd-flex-horizontal {}
.bd-flex-reverse {}
.bd-flex-horizontal > .spacer-2upayl,
.bd-flex-reverse > .spacer-2upayl,
.bd-flex-vertical > .spacer-2upayl {
min-height: 1px;
}
.flexCenter-1Mwsxg {}
.bd-flex {}
.bd-flex-horizontal {}
.bd-flex-reverse {}
.bd-flex-horizontal > .bd-flex,
.bd-flex-horizontal > .bd-flex-child {
margin-left: 10px;
margin-right: 10px;
}
.bd-flex-horizontal > .bd-flex:first-child,
.bd-flex-horizontal > .bd-flex-child:first-child {
margin-left: 0;
}
.bd-flex-horizontal > .bd-flex:last-child,
.bd-flex-horizontal > .bd-flex-child:last-child {
margin-right: 0;
}
.bd-flex-reverse > .bd-flex,
.bd-flex-reverse > .bd-flex-child {
margin-left: 10px;
margin-right: 10px;
}
.bd-flex-reverse > .bd-flex:first-child,
.bd-flex-reverse > .bd-flex-child:first-child {
margin-right: 0;
}
.bd-flex-reverse > .bd-flex:last-child,
.bd-flex-reverse > .bd-flex-child:last-child {
margin-left: 0;
}

View File

@ -71,3 +71,143 @@
@keyframes bd-modal-open {
from {transform: scale(0.7);}
}
.bd-modal-root {
display: flex;
flex-direction: column;
background-color: var(--modal-background);
border-radius: 4px;
margin: 0 auto;
pointer-events: all;
position: relative;
max-height: 100%;
}
.bd-close-button {
height: 26px;
padding: 4px;
transition: opacity 0.2s ease-in-out;
opacity: 0.5;
cursor: pointer;
border-radius: 3px;
color: var(--interactive-normal);
box-sizing: content-box;
}
.bd-close-button:hover {
opacity: 1;
color: var(--interactive-hover);
}
.bd-modal-small {
width: 440px;
max-height: 720px;
min-height: 200px;
}
.bd-modal-standard {
font-size: 13px;
white-space: pre-wrap;
word-wrap: break-word;
width: 490px;
max-height: 800px;
}
.bd-modal-medium {
width: 600px;
max-height: 800px;
min-height: 400px;
}
.bd-modal-large {
width: 800px;
max-height: 960px;
min-height: 400px;
}
.bd-modal-header,
.bd-modal-footer {
position: relative;
flex: 0 0 auto;
padding: 16px;
z-index: 1;
overflow-x: hidden;
}
.bd-modal-header {
border-radius: 4px 4px 0 0;
transition: box-shadow 0.1s ease-out;
word-wrap: break-word;
}
.bd-modal-footer {
border-radius: 0 0 5px 5px;
background-color: var(--modal-footer-background);
overflow: hidden;
box-shadow: inset 0 1px 0 hsl(var(--primary-630-hsl)/0.6);
}
.bd-modal-content {
position: relative;
z-index: 0;
border-radius: 5px 5px 0 0;
padding-left: 16px;
/* padding-right: 16px; */
overflow-x: hidden;
font-size: 16px;
line-height: 20px;
padding-bottom: 20px;
overflow: hidden scroll;
padding-right: 8px;
}
.bd-modal-backdrop {
position: fixed;
top: 0;
right: var(--devtools-sidebar-width,0);
bottom: 0;
left: 0;
-webkit-transform: translateZ(0);
transform: translateZ(0);
pointer-events: all;
}
#bd-modal-container {
position: absolute;
top: 0;
left: 0;
right: var(--devtools-sidebar-width,0);
bottom: 0;
background: none!important;
pointer-events: none;
z-index: 1002;
}
.bd-modal-layer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.bd-modal-layer {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
min-height: 0;
padding-top: 40px;
padding-bottom: 40px;
}

View File

@ -0,0 +1,34 @@
.bd-scroller-base {
position: relative;
box-sizing: border-box;
min-height: 0;
flex: 1 1 auto;
}
.bd-scroller-thin {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thin-thumb) var(--scrollbar-thin-track);
}
.bd-scroller-thin::-webkit-scrollbar-track {
border-color: var(--scrollbar-thin-track);
background-color: var(--scrollbar-thin-track);
border: 2px solid var(--scrollbar-thin-track);
}
.bd-scroller-thin::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.bd-scroller-thin::-webkit-scrollbar-corner {
background-color: transparent;
}
.bd-scroller-thin::-webkit-scrollbar-thumb {
background-clip: padding-box;
border: 2px solid transparent;
border-radius: 4px;
background-color: var(--scrollbar-thin-thumb);
min-height: 40px;
}

View File

@ -0,0 +1,103 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
// S.Looks = y;
// S.Colors = I;
// S.BorderColors = O;
// S.Hovers = T;
// S.Sizes = v;
const {useCallback} = React;
export const Looks = Object.freeze({
FILLED: "bd-button-filled",
OUTLINED: "bd-button-outlined",
LINK: "bd-button-link",
BLANK: "bd-button-blank"
});
export const Colors = Object.freeze({
BRAND: "bd-button-color-brand",
BLURPLE: "bd-button-color-blurple",
RED: "bd-button-color-red",
GREEN: "bd-button-color-green",
YELLOW: "bd-button-color-yellow",
PRIMARY: "bd-button-color-primary",
LINK: "bd-button-color-link",
WHITE: "bd-button-color-white",
TRANSPARENT: "bd-button-color-transparent",
CUSTOM: ""
});
export const Sizes = Object.freeze({
NONE: "",
TINY: "bd-button-tiny",
SMALL: "bd-button-small",
MEDIUM: "bd-button-medium",
LARGE: "bd-button-large",
ICON: "bd-button-icon"
});
export default function Button({
className,
children,
onClick,
onKeyDown,
buttonRef,
disabled = false,
type = "button",
look = Looks.FILLED,
color = Colors.BRAND,
size = Sizes.MEDIUM,
grow = true
}) {
const handleClick = useCallback(event => {
event.preventDefault();
event.stopPropagation();
onClick?.(event);
}, [onClick]);
return <button className={
Utilities.className(
"bd-button",
className,
look,
color,
size,
grow ? "bd-button-grow" : ""
)}
ref={buttonRef}
type={type === "button" ? null : type}
onClick={disabled ? () => {} : handleClick}
onKeyDown={disabled ? () => {} : onKeyDown}
>
<div className="bd-button-content">{children}</div>
</button>;
}
Button.Looks = Looks;
Button.Colors = Colors;
Button.Sizes = Sizes;
// window.BDButton = Button;
// (() => {
// const buttons = [];
// for (const look in window.BDButton.Looks) {
// if (!window.BDButton.Looks[look] || look === "BLANK") continue;
// for (const color in window.BDButton.Colors) {
// if (!window.BDButton.Colors[color]) continue;
// for (const size in window.BDButton.Sizes) {
// if (!window.BDButton.Sizes[size]) continue;
// buttons.push(window.BdApi.React.createElement(window.BDButton, {
// look: window.BDButton.Looks[look],
// color: window.BDButton.Colors[color],
// size: window.BDButton.Sizes[size]
// }, "Hello World!"));
// buttons.push(window.BdApi.React.createElement("br"));
// }
// }
// }
// window.BdApi.showConfirmationModal("Buttons", buttons);
// })();

View File

@ -0,0 +1,76 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
export const Direction = Object.freeze({
VERTICAL: "bd-flex-vertical",
HORIZONTAL: "bd-flex-horizontal",
HORIZONTAL_REVERSE: "bd-flex-reverse"
});
export const Justify = Object.freeze({
START: "bd-flex-justify-start",
END: "bd-flex-justify-end",
CENTER: "bd-flex-justify-center",
BETWEEN: "bd-flex-justify-between",
AROUND: "bd-flex-justify-around"
});
export const Align = Object.freeze({
START: "bd-flex-align-start",
END: "bd-flex-align-end",
CENTER: "bd-flex-align-center",
STRETCH: "bd-flex-align-stretch",
BASELINE: "bd-flex-align-baseline"
});
export const Wrap = Object.freeze({
NO_WRAP: "bd-flex-no-wrap",
WRAP: "bd-flex-wrap",
WRAP_REVERSE: "bd-flex-wrap-reverse"
});
export function Child(props) {
if (!props.className) props.className = "";
props.className = Utilities.className(props.className, "bd-flex-child");
return <Flex {...props} />;
}
export default function Flex({
children,
className,
style,
shrink = 1,
grow = 1,
basis = "auto",
direction = Direction.HORIZONTAL,
align = Align.STRETCH,
justify = Justify.START,
wrap = Wrap.NO_WRAP
}) {
return <div
className={Utilities.className(
"bd-flex",
direction,
justify,
align,
wrap,
className
)}
style={Object.assign({
flexShrink: shrink,
flexGrow: grow,
flexBasis: basis
}, style)}
>
{children}
</div>;
}
Flex.Child = Child;
Flex.Direction = Direction;
Flex.Align = Align;
Flex.Justify = Justify;
Flex.Wrap = Wrap;

View File

@ -0,0 +1,35 @@
import React from "@modules/react";
import WebpackModules from "@modules/webpackmodules";
import DiscordModules from "@modules/discordmodules";
const DiscordMarkdown = WebpackModules.find(m => m?.prototype?.render && m.rules);
let rules = {};
if (DiscordMarkdown) {
rules = {
...DiscordMarkdown.rules,
link: DiscordModules.SimpleMarkdown.defaultRules.link
};
const originalLink = rules.link.react;
rules.link.react = function() {
const original = Reflect.apply(originalLink, undefined, arguments);
original.props.className = "bd-link";
original.props.target = "_blank";
original.props.rel = "noopener noreferrer";
return original;
};
}
export default function Markdown({className, children}) {
if (!DiscordMarkdown) return <div className="bd-markdown-fallback">{children}</div>;
return <DiscordMarkdown
className={className}
parser={DiscordModules.SimpleMarkdown.parserFor(rules)}
output={DiscordModules.SimpleMarkdown.reactFor(DiscordModules.SimpleMarkdown.ruleOutput(rules, "react"))}
>
{children}
</DiscordMarkdown>;
}

View File

@ -0,0 +1,55 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
export const Colors = Object.freeze({
STANDARD: "bd-text-normal",
MUTED: "bd-text-muted",
ERROR: "bd-text-error",
BRAND: "bd-text-brand",
LINK: "bd-text-link",
HEADER_PRIMARY: "bd-header-primary",
HEADER_SECONDARY: "bd-header-secondary",
STATUS_YELLOW: "bd-text-yellow",
STATUS_GREEN: "bd-text-green",
STATUS_RED: "bd-text-red",
ALWAYS_WHITE: "bd-text-white",
CUSTOM: null
});
export const Sizes = Object.freeze({
SIZE_10: "bd-text-10",
SIZE_12: "bd-text-12",
SIZE_14: "bd-text-14",
SIZE_16: "bd-text-16",
SIZE_20: "bd-text-20",
SIZE_24: "bd-text-24",
SIZE_32: "bd-text-32"
});
export default function Text({tag: Tag = "div", className, children, color = Colors.STANDARD, size = Sizes.SIZE_14, selectable, strong, style}) {
return <Tag
className={
Utilities.className(
color, size, className,
{
"bd-selectable": selectable,
"bd-text-strong": strong
}
)}
style={style}
>
{children}
</Tag>;
}
Text.Colors = Colors;
Text.Sizes = Sizes;
// te = WebpackModules.getModule(m => m?.Sizes?.SIZE_32 && m.Colors)
// foo = []
// for (const color in te.Colors) foo.push(BdApi.React.createElement(te, {color: te.Colors[color]}, color))
// for (const size in te.Sizes) foo.push(BdApi.React.createElement(te, {size: te.Sizes[size]}, size))
// BdApi.showConfirmationModal("Text Elements", foo)

View File

@ -1,13 +1,15 @@
import SimpleMarkdown from "@structs/markdown";
import React from "@modules/react";
import DiscordClasses from "@modules/discordclasses";
import WebpackModules from "@modules/webpackmodules";
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

@ -7,18 +7,30 @@ import React from "@modules/react";
import ReactDOM from "@modules/reactdom";
import Strings from "@modules/strings";
import Settings from "@modules/settingsmanager";
import Events from "@modules/emitter";
import DiscordModules from "@modules/discordmodules";
import WebpackModules from "@modules/webpackmodules";
import DiscordClasses from "@modules/discordclasses";
import DOMManager from "@modules/dommanager";
import AddonErrorModal from "./addonerrormodal";
import AddonErrorModal from "./modals/addonerrormodal";
import ErrorBoundary from "./errorboundary";
import TextElement from "./base/text";
import ModalRoot from "./modals/root";
import ModalHeader from "./modals/header";
import ModalContent from "./modals/content";
import ModalFooter from "./modals/footer";
import ConfirmationModal from "./modals/confirmation";
import Button from "./base/button";
import CustomMarkdown from "./base/markdown";
import ChangelogModal from "./modals/changelog";
import ModalStack, {generateKey} from "./modals/stack";
export default class Modals {
static get shouldShowAddonErrors() {return Settings.get("settings", "addons", "addonErrors");}
static get hasModalOpen() {return !!document.getElementsByClassName("bd-modal").length;}
static get ModalActions() {
return this._ModalActions ??= {
@ -26,21 +38,11 @@ export default class Modals {
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") && 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"), {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 ??= [];}
static get hasModalOpen() {return !!document.getElementsByClassName("bd-modal").length;}
static async initialize() {
const names = ["ConfirmationModal", "ModalActions", "Markdown", "ModalRoot", "ModalComponents", "Buttons", "TextElement", "FlexElements"];
const names = ["ModalActions"];
for (const name of names) {
let value = this[name];
@ -164,8 +166,6 @@ export default class Modals {
* @returns {string} - the key used for this modal
*/
static showConfirmationModal(title, content, options = {}) {
const Markdown = this.Markdown;
const ConfirmationModal = this.ConfirmationModal;
const ModalActions = this.ModalActions;
if (content instanceof FormattableString) content = content.toString();
@ -173,7 +173,7 @@ export default class Modals {
const emptyFunction = () => {};
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) {
if (!this.ModalActions) {
return this.default(title, content, [
confirmText && {label: confirmText, action: onConfirm},
cancelText && {label: cancelText, action: onCancel, danger}
@ -181,9 +181,9 @@ export default class Modals {
}
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(CustomMarkdown, null, c) : c);
const modalKey = ModalActions.openModal(props => {
const modalKey = this.openModal(props => {
return React.createElement(ErrorBoundary, {
onError: () => {
setTimeout(() => {
@ -196,7 +196,7 @@ export default class Modals {
}
}, React.createElement(ConfirmationModal, Object.assign({
header: title,
confirmButtonColor: danger ? this.Buttons.Colors.RED : this.Buttons.Colors.BRAND,
danger: danger,
confirmText: confirmText,
cancelText: cancelText,
onConfirm: onConfirm,
@ -209,87 +209,22 @@ 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(this.ModalRoot, Object.assign(props, {
size: "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(this.ModalComponents.Footer, {
className: "bd-error-modal-footer",
}, React.createElement(this.Buttons, {
onClick: props.onClose,
className: "bd-button"
}, Strings.Modals.okay))
]
}))));
const options = {
ref: this.addonErrorsRef,
pluginErrors: Array.isArray(pluginErrors) ? pluginErrors : [],
themeErrors: Array.isArray(themeErrors) ? themeErrors : []
};
this.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(AddonErrorModal, Object.assign(options, props)));
});
}
static showChangelogModal(options = {}) {
const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen", "root");
const ChangelogModalClasses = WebpackModules.getModule(m => m.modal && m.maxModalWidth);
const ChangelogClasses = WebpackModules.getByProps("fixed", "improved") ?? {maxModalWidth: "490px",video: "video-8B-TdZ",container: "container-3PVapX",image: "image-ZPv20Y",title: "title-2ftWWc",lead: "lead-2VtcIe",added: "added-mQcv9V title-2ftWWc",fixed: "fixed-cTX7Hp title-2ftWWc",improved: "improved-2SJXHz title-2ftWWc",progress: "progress-1DcfFh title-2ftWWc",marginTop: "marginTop-VGmU1T",footer: "footer-1gMODG",socialLink: "socialLink-1qjJIk",premiumBanner: "premiumBanner-FU1Urp",premiumIcon: "premiumIcon-rhwgnW",date: "date-2tmzZM"};
const TextElement = this.TextElement;
const FlexChild = this.FlexElements;
const MarkdownParser = WebpackModules.getByProps("defaultRules", "parse");
options = Object.assign({image: "https://i.imgur.com/wuh5yMK.png", description: "", changes: [], title: "BetterDiscord", subtitle: `v${Config.version}`}, options);
if (!OriginalModalClasses || !ChangelogModalClasses || !ChangelogClasses || !TextElement || !FlexChild || !MarkdownParser) return Logger.warn("Modals", "showChangelogModal missing modules");
const {image = "https://i.imgur.com/wuh5yMK.png", description = "", changes = [], title = "BetterDiscord", subtitle = `v${Config.version}`, footer} = options;
const ce = React.createElement;
const changelogItems = [options.video ? ce("video", {src: options.video, poster: options.poster, controls: true, className: ChangelogClasses.video}) : ce("img", {src: image})];
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));
if (entry.description) changelogItems.push(ce("p", null, MarkdownParser.parse(entry.description)));
const list = ce("ul", null, entry.items.map(i => ce("li", null, MarkdownParser.parse(i))));
changelogItems.push(list);
}
const renderHeader = function() {
return ce(FlexChild, {className: OriginalModalClasses.header, grow: 0, shrink: 0, direction: FlexChild.Direction.VERTICAL},
ce(TextElement, {tag: "h1", size: TextElement.Sizes.SIZE_20, strong: true}, title),
ce(TextElement, {size: TextElement.Sizes.SIZE_12, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
);
};
const renderFooter = () => {
const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
const joinSupportServer = (click) => {
click.preventDefault();
click.stopPropagation();
DiscordModules.InviteActions.acceptInviteAndTransitionToInviteChannel({inviteKey: "0Tmfo5ZbORCRqbAd"});
};
const supportLink = ce("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
const defaultFooter = ce(TextElement, {size: TextElement.Sizes.SIZE_12, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
return ce(FlexChild, {className: OriginalModalClasses.footer + " " + OriginalModalClasses.footerSeparator},
ce(FlexChild.Child, {grow: 1, shrink: 1}, footer ? footer : defaultFooter)
);
};
const body = ce("div", {
className: `${OriginalModalClasses.content} ${ChangelogClasses.container} ${ChangelogModalClasses.content} ${DiscordClasses.Scrollers.thin}`
}, changelogItems);
const key = this.ModalActions.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(this.ModalRoot, Object.assign({
className: `bd-changelog-modal ${OriginalModalClasses.root} ${OriginalModalClasses.small} ${ChangelogModalClasses.modal}`,
selectable: true,
onScroll: _ => _,
onClose: _ => _,
}, props), renderHeader(), body, renderFooter()));
const key = this.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(ChangelogModal, Object.assign(options, props)));
});
return key;
}
@ -315,7 +250,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
@ -327,23 +262,35 @@ export default class Modals {
}
if (typeof(child) === "function") child = React.createElement(child);
const mc = this.ModalComponents;
const modal = props => {
return React.createElement(ErrorBoundary, {}, React.createElement(this.ModalRoot, Object.assign({size: mc.Sizes.MEDIUM, className: "bd-addon-modal" + " " + mc.Sizes.MEDIUM}, props),
React.createElement(mc.Header, {separator: false, className: "bd-addon-modal-header"},
React.createElement(this.TextElement, {tag: "h1", size: this.TextElement.Sizes.SIZE_20, strong: true}, `${name} Settings`)
),
React.createElement(mc.Content, {className: "bd-addon-modal-settings"},
React.createElement(ErrorBoundary, {}, child)
),
React.createElement(mc.Footer, {className: "bd-addon-modal-footer"},
React.createElement(this.Buttons, {onClick: props.onClose, className: "bd-button"}, 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));
});
}
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 = {}) {
if (!this.hasInitialized) this.makeStack();
options.modalKey = generateKey(options.modalKey);
Events.emit("open-modal", render, options);
return options.modalKey;
}
}
Modals.makeStack();

View File

@ -1,13 +1,21 @@
import React from "@modules/react";
import Strings from "@modules/strings";
import DiscordClasses from "@modules/discordclasses";
import WebpackModules from "@modules/webpackmodules";
import Text from "@ui/base/text";
import Button from "@ui/base/button";
import Flex from "@ui/base/flex";
import Extension from "@ui/icons/extension";
import ThemeIcon from "@ui/icons/theme";
import Divider from "@ui/divider";
import Header from "./header";
import Content from "./content";
import ModalRoot from "./root";
import Footer from "./footer";
const Parser = Object(WebpackModules.getByProps("defaultRules", "parse")).defaultRules;
const {useState, useCallback, useMemo} = React;
@ -35,12 +43,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">
@ -56,7 +64,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),
@ -68,17 +76,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

@ -0,0 +1,35 @@
import React from "@modules/react";
import WebpackModules from "@modules/webpackmodules";
import Utilities from "@modules/utilities";
const Spring = WebpackModules.getByProps("useSpring", "animated");
export default function Backdrop({isVisible, className, onClick}) {
const transition = Spring.useTransition(isVisible, {
keys: e => e ? "backdrop" : "empty",
config: {duration: 300},
from: {
opacity: 0,
background: "var(--black-500)"
},
enter: {
opacity: 0.85,
background: "var(--black-500)"
},
leave: {
opacity: 0,
background: "var(--black-500)"
}
});
return transition((styles, visible) => {
if (!visible) return null;
return <Spring.animated.div
className={Utilities.className("bd-modal-backdrop", className)}
style={styles}
onClick={onClick}
/>;
});
}

View File

@ -0,0 +1,66 @@
import React from "@modules/react";
import WebpackModules from "@modules/webpackmodules";
import DiscordModules from "@modules/discordmodules";
import Root from "./root";
import Header from "./header";
import Footer from "./footer";
import Content from "./content";
import Flex from "../base/flex";
import Text from "../base/text";
import CloseButton from "./close";
import SimpleMarkdownExt from "@structs/markdown";
const {useMemo} = React;
const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
const joinSupportServer = (click) => {
click.preventDefault();
click.stopPropagation();
DiscordModules.InviteActions.acceptInviteAndTransitionToInviteChannel({inviteKey: "0Tmfo5ZbORCRqbAd"});
};
const supportLink = <a className={`${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`} onClick={joinSupportServer}>Join our Discord Server.</a>;
const defaultFooter = <Text>Need support? {supportLink}</Text>;
export default function ChangelogModal({transitionState, footer, title, subtitle, onClose, video, poster, image, description, changes}) {
const ChangelogHeader = useMemo(() => <Header justify={Flex.Justify.BETWEEN}>
<Flex direction={Flex.Direction.VERTICAL}>
<Text tag="h1" size={Text.Sizes.SIZE_20} strong={true}>{title}</Text>
<Text size={Text.Sizes.SIZE_12} color={Text.Colors.HEADER_SECONDARY}>{subtitle}</Text>
</Flex>
<CloseButton onClick={onClose} />
</Header>, [title, subtitle, onClose]);
const ChangelogFooter = useMemo(() => <Footer>
<Flex.Child grow="1" shrink="1">
{footer ? footer : defaultFooter}
</Flex.Child>
</Footer>, [footer]);
const changelogItems = useMemo(() => {
const items = [video ? <video src={video} poster={poster} controls={true} className="bd-changelog-poster" /> : <img src={image} className="bd-changelog-poster" />];
if (description) items.push(<p>{SimpleMarkdownExt.parseToReact(description)}</p>);
for (let c = 0; c < changes.length; c++) {
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>);
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);
}
return items;
}, [description, video, image, poster, changes]);
return <Root className="bd-changelog-modal" transitionState={transitionState} size={Root.Sizes.SMALL} style={Root.Styles.STANDARD}>
{ChangelogHeader}
<Content>{changelogItems}</Content>
{ChangelogFooter}
</Root>;
}

View File

@ -0,0 +1,17 @@
import React from "@modules/react";
import Button from "../base/button";
import Close from "../icons/close";
export default function CloseButton({onClick}) {
return <Button
className="bd-close-button"
size={Button.Sizes.ICON}
look={Button.Looks.BLANK}
color={Button.Colors.TRANSPARENT}
onClick={onClick}
>
<Close size="24px" />
</Button>;
}

View File

@ -0,0 +1,54 @@
import React from "@modules/react";
import Strings from "@modules/strings";
import Root from "./root";
import Header from "./header";
import Footer from "./footer";
import Content from "./content";
import Text from "../base/text";
import Button from "../base/button";
const {useRef, useEffect} = React;
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);
}, []);
const buttonRef = useRef(null);
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
type="submit"
buttonRef={buttonRef}
color={danger ? Button.Colors.RED : Button.Colors.BRAND}
onClick={() => {
onConfirm?.();
onClose();
}}
>
{confirmText}
</Button>}
{cancelText && <Button
type="button"
look={Button.Looks.LINK}
color={Button.Colors.PRIMARY}
onClick={() => {
onCancel?.();
onClose();
}}
>
{cancelText}
</Button>}
</Footer>
</Root>;
}

View File

@ -0,0 +1,9 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
export default function Content({id, className, children, scroller = true}) {
return <div id={id} className={Utilities.className("bd-modal-content", {"bd-scroller-base bd-scroller-thin": scroller}, className)}>
{children}
</div>;
}

View File

@ -0,0 +1,20 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
import Flex from "../base/flex";
export default function Footer({id, className, children}) {
return <Flex
id={id}
className={Utilities.className("bd-modal-footer", className)}
grow={0}
shrink={0}
direction={Flex.Direction.HORIZONTAL_REVERSE}
justify={Flex.Justify.START}
align={Flex.Align.STRETCH}
wrap={Flex.Wrap.NO_WRAP}
>
{children}
</Flex>;
}

View File

@ -0,0 +1,20 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
import Flex from "../base/flex";
export default function Header({id, className, children}) {
return <Flex
id={id}
className={Utilities.className("bd-modal-header", className)}
grow={0}
shrink={0}
direction={Flex.Direction.HORIZONTAL}
justify={Flex.Justify.START}
align={Flex.Align.CENTER}
wrap={Flex.Wrap.NO_WRAP}
>
{children}
</Flex>;
}

View File

@ -0,0 +1,88 @@
import React from "@modules/react";
import Utilities from "@modules/utilities";
import WebpackModules from "@modules/webpackmodules";
const Spring = WebpackModules.getByProps("useSpring", "animated");
const Anims = WebpackModules.getByProps("Easing");
export const Sizes = Object.freeze({
SMALL: "bd-modal-small",
MEDIUM: "bd-modal-medium",
LARGE: "bd-modal-large",
DYNAMIC: ""
});
export const Styles = Object.freeze({
STANDARD: "bd-modal-standard",
CUSTOM: ""
});
export default function ModalRoot({className, transitionState, children, size = Sizes.DYNAMIC, style = Styles.CUSTOM}) {
const visible = transitionState == 0 || transitionState == 1; // 300 ms
// const visible = transitionState;
const springStyles = Spring.useSpring({
opacity: visible ? 1 : 0,
transform: visible ? "scale(1)" : "scale(0.7)",
config: {
duration: visible ? 300 : 100,
easing: visible ? Anims.Easing.inOut(Anims.Easing.back()) : Anims.Easing.quad,
clamp: true
}
});
return <Spring.animated.div
className={Utilities.className("bd-modal-root", size, className, style)}
style={springStyles}
>
{children}
</Spring.animated.div>;
// const [visible, setVisible] = React.useState(true);
// const visible = transitionState < 2;
// const springTransition = Spring.useTransition(transitionState, {
// keys: e => e ? "backdrop" : "empty",
// from: {
// opacity: 0,
// transform: "scale(0.7)"
// },
// enter: {
// opacity: 1,
// transform: "scale(1)"
// },
// leave: {
// opacity: 0,
// transform: "scale(0.7)"
// },
// // config: (a, b, c, d) => {
// // console.log({a, b, c, d});
// // return {
// // duration: a ? 300 : 100,
// // easing: a ? Anims.Easing.inOut(Anims.Easing.back()) : Anims.Easing.quad,
// // clamp: true
// // };
// // }
// config: (a, b, c) => {
// console.log({a, b, c});
// return {
// duration: true ? 300 : 100,
// easing: true ? Anims.Easing.inOut(Anims.Easing.back()) : Anims.Easing.quad,
// clamp: true
// };
// }
// });
// return springTransition((styles, isVisible) => {
// if (!isVisible) console.log("not visible");
// return <Spring.animated.div
// className={Utilities.className("bd-modal-root", size, className, style)}
// style={styles}
// >
// {children}
// </Spring.animated.div>;
// });
}
ModalRoot.Sizes = Sizes;
ModalRoot.Styles = Styles;

View File

@ -0,0 +1,76 @@
import React from "@modules/react";
import Events from "@modules/emitter";
import WebpackModules from "@modules/webpackmodules";
import Backdrop from "./backdrop";
const {Fragment, useState, useCallback, useEffect} = React;
const Transitions = WebpackModules.getModule(m => m?.defaultProps?.transitionAppear);
// const Transitions = WebpackModules.getByProps("TransitionGroup").TransitionGroup;
class ModalLayer extends React.Component {
constructor(props) {
super(props);
this.state = {transitionState: null};
}
componentWillEnter(finish) {
this.setState({transitionState: 0});
setTimeout(() => {
this.setState({transitionState: 1});
finish();
}, 300);
}
componentWillLeave(finish) {
this.setState({transitionState: 2});
setTimeout(() => {
this.setState({transitionState: 3});
finish();
}, 300);
}
render() {
return React.createElement("div", {className: "bd-modal-layer"}, this.props.render({
transitionState: this.state.transitionState,
onClose: this.props.onClose
}));
}
}
// ENTERING 0
// ENTERED 1
// EXITING 2
// EXITED 3
// HIDDEN 4
let modalKey = 0;
export const generateKey = key => key ? `${key}-${modalKey++}` : modalKey++;
export default function ModalStack() {
const [modals, setModals] = useState([]);
const addModal = useCallback((render, props = {}) => {
setModals(m => [...m, {...props, render}]);
}, []);
const removeModal = useCallback((key) => {
setModals(mods => mods.filter(m => {
if (m.modalKey === key && m.onClose) m.onClose();
return m.modalKey !== key;
}));
}, []);
useEffect(() => {
Events.on("open-modal", addModal);
return () => {
Events.off("open-modal", addModal);
};
}, [addModal]);
return <Transitions component={Fragment}>
<Backdrop isVisible={!!modals.length} onClick={() => removeModal(modals[modals.length - 1].modalKey)} />
{modals.length && <ModalLayer key={modals[modals.length - 1].modalKey} {...modals[modals.length - 1]} onClose={() => removeModal(modals[modals.length - 1].modalKey)} />}
</Transitions>;
}

View File

@ -65,7 +65,7 @@ function makeButton(title, children, action, {isControl = false, danger = false,
const ButtonType = isControl ? "button" : "div";
return <DiscordModules.Tooltip color="primary" position="top" text={title}>
{(props) => {
return <ButtonType {...props} className={(isControl ? "bd-button bd-addon-button" : "bd-addon-button") + (danger ? " bd-button-danger" : "") + (disabled ? " bd-button-disabled" : "")} onClick={action}>{children}</ButtonType>;
return <ButtonType {...props} className={(isControl ? "bd-button bd-button-filled bd-addon-button" : "bd-addon-button") + (danger ? " bd-button-color-red" : isControl ? " bd-button-color-brand" : "") + (disabled ? " bd-button-disabled" : "")} onClick={action} disabled={disabled}>{children}</ButtonType>;
}}
</DiscordModules.Tooltip>;
}

View File

@ -1,5 +1,7 @@
import React from "@modules/react";
import Button from "../base/button";
const {useCallback} = React;
@ -18,7 +20,7 @@ export default function SettingsTitle({isGroup, className, button, onClick, text
const titleClass = className ? `${baseClass} ${className}` : baseClass;
return <h2 className={titleClass} onClick={() => {onClick?.();}}>
{text}
{button && <button className="bd-button bd-button-title" onClick={click}>{button.title}</button>}
{button && <Button className="bd-button-title" onClick={click} size={Button.Sizes.NONE}>{button.title}</Button>}
{otherChildren}
</h2>;