1Lighty-BetterDiscordPlugins/Plugins/1XenoLib.plugin.js

1686 lines
78 KiB
JavaScript
Raw Normal View History

2020-02-27 21:05:23 +01:00
//META{"name":"XenoLib","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js/","authorId":"239513071272329217","invite":"NYvWdN5","donate":"https://paypal.me/lighty13"}*//
2019-12-21 21:16:47 +01:00
/*@cc_on
@if (@_jscript)
// Offer to self-install for clueless users that try to run this directly.
var shell = WScript.CreateObject('WScript.Shell');
var fs = new ActiveXObject('Scripting.FileSystemObject');
var pathPlugins = shell.ExpandEnvironmentStrings('%APPDATA%\\BetterDiscord\\plugins');
var pathSelf = WScript.ScriptFullName;
// Put the user at ease by addressing them in the first person
shell.Popup('It looks like you\'ve mistakenly tried to run me directly. \n(Don\'t do that!)', 0, 'I\'m a plugin for BetterDiscord', 0x30);
if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
2020-03-31 14:17:23 +02:00
shell.Popup('I\'m in the correct folder already.', 0, 'I\'m already installed', 0x40);
2019-12-21 21:16:47 +01:00
} else if (!fs.FolderExists(pathPlugins)) {
shell.Popup('I can\'t find the BetterDiscord plugins folder.\nAre you sure it\'s even installed?', 0, 'Can\'t install myself', 0x10);
} else if (shell.Popup('Should I copy myself to BetterDiscord\'s plugins folder for you?', 0, 'Do you need some help?', 0x34) === 6) {
fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
// Show the user where to put plugins in the future
shell.Exec('explorer ' + pathPlugins);
2020-03-31 14:17:23 +02:00
shell.Popup('I\'m installed!', 0, 'Successfully installed', 0x40);
2019-12-21 21:16:47 +01:00
}
WScript.Quit();
@else@*/
/*
2020-01-04 18:55:34 +01:00
* Copyright © 2019-2020, _Lighty_
2019-12-21 21:16:47 +01:00
* All rights reserved.
* Code may not be redistributed, modified or otherwise taken without explicit permission.
*/
var XenoLib = (() => {
2019-12-24 12:58:53 +01:00
/* Setup */
const config = {
main: 'index.js',
info: {
name: 'XenoLib',
authors: [
{
name: 'Lighty',
discord_id: '239513071272329217',
github_username: 'LightyPon',
twitter_username: ''
}
],
2020-03-31 14:17:23 +02:00
version: '1.3.16',
2019-12-24 12:58:53 +01:00
description: 'Simple library to complement plugins with shared code without lowering performance.',
github: 'https://github.com/1Lighty',
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js'
},
changelog: [
{
2020-01-04 18:55:34 +01:00
title: 'Boring changes',
2020-03-01 19:26:58 +01:00
type: 'fixed',
2020-03-31 14:17:23 +02:00
items: ['Removed usage of soon to be deprecated globals.', 'Fixed random notification bounce, again. For real this time.', 'Fixed oversized close button on notifications.', 'Changed right click behavior of the close button on notifications to just close all notifications outright. Left click still only closes 1.']
2020-01-04 18:55:34 +01:00
}
],
defaultConfig: [
{
type: 'category',
id: 'notifications',
name: 'Notification settings',
collapsible: true,
shown: true,
settings: [
{
name: 'Notification position',
id: 'position',
type: 'position',
value: 'topRight'
2020-02-27 21:05:23 +01:00
},
{
name: 'Notifications use backdrop-filter',
id: 'backdrop',
type: 'switch',
value: true
2020-03-01 19:26:58 +01:00
},
{
name: 'Backdrop color',
id: 'backdropColor',
type: 'color',
2020-03-03 16:31:04 +01:00
value: '#3e4346',
options: {
defaultColor: '#3e4346'
}
2020-03-01 19:26:58 +01:00
},
{
name: 'Timeout resets to 0 when hovered',
id: 'timeoutReset',
type: 'switch',
value: true
2020-01-04 18:55:34 +01:00
}
]
2019-12-24 12:58:53 +01:00
}
]
};
/* Build */
const buildPlugin = ([Plugin, Api]) => {
const { ContextMenu, EmulatedTooltip, Toasts, Settings, Popouts, Modals, Utilities, WebpackModules, Filters, DiscordModules, ColorConverter, DOMTools, DiscordClasses, DiscordSelectors, ReactTools, ReactComponents, DiscordAPI, Logger, Patcher, PluginUpdater, PluginUtilities, DiscordClassModules, Structs } = Api;
2020-03-01 19:26:58 +01:00
const { React, ModalStack, ContextMenuActions, ContextMenuItem, ContextMenuItemsGroup, ReactDOM, ChannelStore, GuildStore, UserStore, DiscordConstants, Dispatcher, GuildMemberStore, GuildActions, PrivateChannelActions, LayerManager, InviteActions, TextElement, FlexChild, Titles, Changelog: ChangelogModal } = DiscordModules;
2019-12-24 12:58:53 +01:00
let CancelledAsync = false;
2020-01-06 12:06:15 +01:00
const DefaultLibrarySettings = {};
for (let s = 0; s < config.defaultConfig.length; s++) {
const current = config.defaultConfig[s];
if (current.type != 'category') {
DefaultLibrarySettings[current.id] = current.value;
} else {
DefaultLibrarySettings[current.id] = {};
for (let s = 0; s < current.settings.length; s++) {
const subCurrent = current.settings[s];
DefaultLibrarySettings[current.id][subCurrent.id] = subCurrent.value;
}
}
}
2020-02-27 21:05:23 +01:00
if (global.XenoLib) {
try {
global.XenoLib.shutdown();
} catch (e) {}
}
2019-12-24 12:58:53 +01:00
const XenoLib = {};
XenoLib.shutdown = () => {
2020-01-06 12:06:15 +01:00
try {
Patcher.unpatchAll();
} catch (e) {
Logger.stacktrace('Failed to unpatch all', e);
}
CancelledAsync = true;
2019-12-24 12:58:53 +01:00
PluginUtilities.removeStyle('XenoLib-CSS');
2020-01-06 12:06:15 +01:00
try {
const notifWrapper = document.querySelector('.xenoLib-notifications');
if (notifWrapper) {
ReactDOM.unmountComponentAtNode(notifWrapper);
notifWrapper.remove();
}
} catch (e) {
Logger.stacktrace('Failed to unmount Notifications component', e);
2020-01-04 18:55:34 +01:00
}
2019-12-21 21:16:47 +01:00
};
2020-03-01 19:26:58 +01:00
XenoLib._ = XenoLib.DiscordUtils = WebpackModules.getByProps('bindAll', 'debounce');
2020-01-06 12:06:15 +01:00
XenoLib.loadData = (name, key, defaultData, returnNull) => {
try {
2020-03-01 19:26:58 +01:00
return XenoLib._.mergeWith(defaultData ? Utilities.deepclone(defaultData) : {}, BdApi.getData(name, key), (_, b) => {
if (XenoLib._.isArray(b)) return b;
});
2020-01-06 12:06:15 +01:00
} catch (err) {
Logger.err(name, 'Unable to load data: ', err);
if (returnNull) return null;
return Utilities.deepclone(defaultData);
}
};
2020-02-03 16:46:45 +01:00
XenoLib.getClass = (arg, thrw) => {
2020-02-03 16:35:41 +01:00
try {
const args = arg.split(' ');
return WebpackModules.getByProps(...args)[args[args.length - 1]];
} catch (e) {
2020-02-03 16:46:45 +01:00
if (thrw) throw e;
2020-02-03 16:35:41 +01:00
if (!XenoLib.getClass.__warns[arg] || Date.now() - XenoLib.getClass.__warns[arg] > 1000 * 60) {
Logger.stacktrace(`Failed to get class with props ${arg}`, e);
XenoLib.getClass.__warns[arg] = Date.now();
}
return '';
}
2020-01-28 20:58:28 +01:00
};
2020-02-03 16:46:45 +01:00
XenoLib.getSingleClass = (arg, thrw) => {
2020-02-03 16:35:41 +01:00
try {
2020-02-08 09:21:54 +01:00
return XenoLib.getClass(arg, thrw).split(' ')[0];
2020-02-03 16:35:41 +01:00
} catch (e) {
2020-02-03 16:46:45 +01:00
if (thrw) throw e;
2020-02-03 16:35:41 +01:00
if (!XenoLib.getSingleClass.__warns[arg] || Date.now() - XenoLib.getSingleClass.__warns[arg] > 1000 * 60) {
Logger.stacktrace(`Failed to get class with props ${arg}`, e);
XenoLib.getSingleClass.__warns[arg] = Date.now();
}
return '';
}
};
XenoLib.getClass.__warns = {};
XenoLib.getSingleClass.__warns = {};
2020-01-28 20:58:28 +01:00
2020-01-06 12:06:15 +01:00
const LibrarySettings = XenoLib.loadData(config.info.name, 'settings', DefaultLibrarySettings);
2019-12-24 12:58:53 +01:00
PluginUtilities.addStyle(
'XenoLib-CSS',
`
2019-12-21 21:16:47 +01:00
.xenoLib-color-picker .xenoLib-button {
width: 34px;
min-height: 38px;
}
.xenoLib-color-picker .xenoLib-button:hover {
width: 128px;
}
2020-01-28 20:58:28 +01:00
.xenoLib-color-picker .xenoLib-button .${XenoLib.getSingleClass('recording text')} {
2019-12-21 21:16:47 +01:00
opacity: 0;
transform: translate3d(200%,0,0);
}
2020-01-28 20:58:28 +01:00
.xenoLib-color-picker .xenoLib-button:hover .${XenoLib.getSingleClass('recording text')} {
2019-12-21 21:16:47 +01:00
opacity: 1;
transform: translateZ(0);
}
.xenoLib-button-icon {
left: 50%;
top: 50%;
position: absolute;
margin-left: -12px;
margin-top: -8px;
width: 24px;
height: 24px;
opacity: 1;
transform: translateZ(0);
transition: opacity .2s ease-in-out,transform .2s ease-in-out,-webkit-transform .2s ease-in-out;
}
.xenoLib-button-icon.xenoLib-revert > svg {
width: 24px;
height: 24px;
}
.xenoLib-button-icon.xenoLib-revert {
margin-top: -12px;
}
.xenoLib-button:hover .xenoLib-button-icon {
opacity: 0;
transform: translate3d(-200%,0,0);
}
2020-01-04 18:55:34 +01:00
.xenoLib-notifications {
position: absolute;
color: white;
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
z-index: 1000;
pointer-events: none;
font-size: 14px;
}
.xenoLib-notification {
min-width: 200px;
overflow: hidden;
}
.xenoLib-notification-content-wrapper {
2020-01-28 20:58:28 +01:00
padding: 22px 20px 0 20px;
2020-01-06 12:06:15 +01:00
}
.xenoLib-centering-bottomLeft .xenoLib-notification-content-wrapper:first-of-type, .xenoLib-centering-bottomMiddle .xenoLib-notification-content-wrapper:first-of-type, .xenoLib-centering-bottomRight .xenoLib-notification-content-wrapper:first-of-type {
padding: 0 20px 20px 20px;
2020-01-04 18:55:34 +01:00
}
.xenoLib-notification-content {
padding: 12px;
overflow: hidden;
background: #474747;
pointer-events: all;
position: relative;
width: 20vw;
2020-03-01 19:26:58 +01:00
white-space: break-spaces;
2020-03-03 16:31:04 +01:00
min-width: 330px;
2020-01-04 18:55:34 +01:00
}
.xenoLib-notification-loadbar {
position: absolute;
bottom: 0;
left: 0px;
width: auto;
background-image: linear-gradient(130deg,var(--grad-one),var(--grad-two));
height: 5px;
}
2020-01-06 12:06:15 +01:00
.xenoLib-notification-loadbar-user {
animation: fade-loadbar-animation 1.5s ease-in-out infinite;
}
@keyframes fade-loadbar-animation {
0% {
filter: brightness(75%)
}
50% {
filter: brightness(100%)
}
to {
filter: brightness(75%)
}
}
2020-01-04 18:55:34 +01:00
.xenoLib-notification-loadbar-striped:before {
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: 5px;
background: linear-gradient(
-20deg,
transparent 35%,
var(--bar-color) 35%,
var(--bar-color) 70%,
transparent 70%
);
animation: shift 1s linear infinite;
background-size: 60px 100%;
box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.2),
inset 0 -2px 1px rgba(0, 0, 0, 0.2);
}
@keyframes shift {
to {
background-position: 60px 100%;
}
}
.xenoLib-notification-close {
float: right;
padding: 0;
height: unset;
2020-03-31 14:17:23 +02:00
opacity: .7;
2020-01-04 18:55:34 +01:00
}
.xenLib-notification-counter {
float: right;
margin-top: 2px;
}
2020-01-06 12:06:15 +01:00
.topMiddle-xenoLib {
top: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
.bottomMiddle-xenoLib {
bottom: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
.xenoLib-centering-topLeft, .xenoLib-centering-bottomLeft {
align-items: flex-start;
}
.xenoLib-centering-topMiddle, .xenoLib-centering-bottomMiddle {
align-items: center;
}
.xenoLib-centering-topRight, .xenoLib-centering-bottomRight {
align-items: flex-end;
}
.xenoLib-centering-bottomLeft, .xenoLib-centering-bottomMiddle, .xenoLib-centering-bottomRight {
flex-direction: column-reverse;
bottom: 0;
}
2020-03-01 19:26:58 +01:00
.XL-chl-p img{
width: unset !important;
}
2020-03-31 14:17:23 +02:00
.xenoLib-error-text {
padding-top: 5px;
}
2019-12-21 21:16:47 +01:00
`
2019-12-24 12:58:53 +01:00
);
2020-03-01 19:26:58 +01:00
2019-12-24 12:58:53 +01:00
XenoLib.joinClassNames = WebpackModules.getModule(e => e.default && e.default.default);
XenoLib.authorId = '239513071272329217';
XenoLib.supportServerId = '389049952732446731';
2020-02-03 16:35:41 +01:00
try {
const getUserAsync = WebpackModules.getByProps('getUser', 'acceptAgreements').getUser;
const requestUser = () =>
getUserAsync(XenoLib.authorId)
.then(user => (XenoLib.author = user))
.catch(() => setTimeout(requestUser, 1 * 60 * 1000));
if (UserStore.getUser(XenoLib.authorId)) XenoLib.author = UserStore.getUser(XenoLib.authorId);
else requestUser();
} catch (e) {
Logger.stacktrace('Failed to grab author object', e);
}
XenoLib.ReactComponents = {};
2019-12-24 12:58:53 +01:00
2020-02-03 16:35:41 +01:00
XenoLib.ReactComponents.ErrorBoundary = class XLErrorBoundary extends React.PureComponent {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(err, inf) {
Logger.err(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`);
this.setState({ hasError: true });
if (typeof this.props.onError === 'function') this.props.onError(err);
}
render() {
if (this.state.hasError) return null;
return this.props.children;
}
2019-12-24 12:58:53 +01:00
};
2020-02-03 16:35:41 +01:00
XenoLib.__contextPatches = [];
try {
if (global.XenoLib) if (global.XenoLib.__contextPatches && global.XenoLib.__contextPatches.length) XenoLib.__contextPatches.push(...global.XenoLib.__contextPatches);
const ContextMenuClassname = XenoLib.getSingleClass('subMenuContext contextMenu'); /* I AM *SPEED* */
const getContextMenuChild = val => {
if (!val) return;
const isValid = obj => obj.type === 'div' && obj.props && typeof obj.props.className === 'string' && obj.props.className.indexOf(ContextMenuClassname) !== -1 && Array.isArray(Utilities.getNestedProp(obj, 'props.children.props.children'));
if (isValid(val)) return val.props.children;
const children = Utilities.getNestedProp(val, 'props.children');
if (!children) return;
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const ret = getContextMenuChild(children[i]);
if (ret) return ret.props.children;
}
} else if (isValid(children)) return children.props.children;
};
2020-01-04 18:55:34 +01:00
const handleContextMenu = (_this, ret, noRender) => {
2019-12-24 12:58:53 +01:00
const menuGroups = getContextMenuChild(ret) || ret;
2020-02-03 16:35:41 +01:00
if (!menuGroups) return /* Logger.warn('Failed to get context menu groups!', _this, ret) */;
/* emulate a react class component */
2020-01-04 18:55:34 +01:00
if (noRender) {
2020-02-03 16:35:41 +01:00
let [value, set] = React.useState(false);
let [state, setState] = React.useState({});
2020-01-04 18:55:34 +01:00
_this.forceUpdate = () => set(!value);
_this.state = state;
_this.setState = setState;
}
if (!_this.state) _this.state = {};
2019-12-24 12:58:53 +01:00
XenoLib.__contextPatches.forEach(e => {
try {
e(_this, menuGroups);
} catch (e) {
Logger.stacktrace('Error with patched context menu', e);
}
});
};
2020-02-03 16:35:41 +01:00
const getModule = regex => {
try {
const modules = WebpackModules.getAllModules();
for (const index in modules) {
if (!modules.hasOwnProperty(index)) continue;
const module = modules[index];
if (!module.exports || !module.exports.__esModule || !module.exports.default) continue;
/* if BDFDB was inited before us, patch the already patched function */
if (module.exports.default.toString().search(regex) !== -1 || (module.exports.default.isBDFDBpatched && module.exports.default.__originalMethod.toString().search(regex) !== -1)) return module;
}
} catch (e) {
Logger.stacktrace(`Failed to getModule by regex ${regex}`, e);
return null;
}
};
2020-02-27 21:05:23 +01:00
const renderContextMenus = ['NativeContextMenu', 'GuildRoleContextMenu', 'DeveloperContextMenu', 'ScreenshareContextMenu'];
2020-02-08 09:21:54 +01:00
const hookContextMenus = [getModule(/case \w.ContextMenuTypes.CHANNEL_LIST_TEXT/), getModule(/case \w.ContextMenuTypes.GUILD_CHANNEL_LIST/), getModule(/case \w.ContextMenuTypes.USER_CHANNEL_MEMBERS/), getModule(/case \w\.ContextMenuTypes\.MESSAGE_MAIN/)];
2020-02-03 16:35:41 +01:00
for (const type of renderContextMenus) {
2019-12-24 12:58:53 +01:00
const module = WebpackModules.getByDisplayName(type);
2020-02-08 09:21:54 +01:00
if (!module) {
2020-02-08 11:40:31 +01:00
Logger.warn(`Failed to find ContextMenu type`, type);
continue;
}
2019-12-24 12:58:53 +01:00
Patcher.after(module.prototype, 'render', (_this, _, ret) => handleContextMenu(_this, ret));
2020-02-03 16:35:41 +01:00
}
for (const menu of hookContextMenus) {
if (!menu) continue;
const origDef = menu.exports.default;
Patcher.after(menu.exports, 'default', (_, [props], ret) => handleContextMenu({ props }, ret, true));
if (origDef.isBDFDBpatched && menu.exports.BDFDBpatch && typeof menu.exports.BDFDBpatch.default.originalMethod === 'function') {
Patcher.after(menu.exports.BDFDBpatch.default, 'originalMethod', (_, [props], ret) => handleContextMenu({ props }, ret, true));
}
}
2020-01-09 20:16:13 +01:00
const GroupDMContextMenu = WebpackModules.getByDisplayName('FluxContainer(GroupDMContextMenu)');
if (GroupDMContextMenu) {
try {
const type = new GroupDMContextMenu({}).render().type;
Patcher.after(type.prototype, 'render', (_this, _, ret) => handleContextMenu(_this, ret));
} catch (e) {
Logger.stacktrace('Failed patching GroupDMContextMenu', e);
}
}
2020-02-03 16:35:41 +01:00
} catch (e) {
Logger.stacktrace('Failed to patch context menus', e);
2019-12-24 12:58:53 +01:00
}
XenoLib.patchContext = callback => {
XenoLib.__contextPatches.push(callback);
};
2020-02-03 16:35:41 +01:00
2019-12-24 12:58:53 +01:00
class ContextMenuWrapper extends React.PureComponent {
render() {
return React.createElement('div', { className: DiscordClasses.ContextMenu.contextMenu }, this.props.menu);
}
}
2020-03-31 14:17:23 +02:00
XenoLib.createSharedContext = (element, type, menuCreation) => {
if (element.__XenoLib_ContextMenus) {
element.__XenoLib_ContextMenus.push(menuCreation);
2019-12-24 12:58:53 +01:00
} else {
2020-03-31 14:17:23 +02:00
element.__XenoLib_ContextMenus = [menuCreation];
const oOnContextMenu = element.props.onContextMenu;
element.props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, _ => React.createElement(ContextMenuWrapper, { menu: element.__XenoLib_ContextMenus.map(m => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'shared context menu' }, m())), type })));
2019-12-24 12:58:53 +01:00
}
};
2019-12-21 21:16:47 +01:00
2020-02-03 16:35:41 +01:00
const ContextMenuSubMenuItem = WebpackModules.getByDisplayName('FluxContainer(SubMenuItem)');
2019-12-24 12:58:53 +01:00
XenoLib.unpatchContext = callback => XenoLib.__contextPatches.splice(XenoLib.__contextPatches.indexOf(callback), 1);
2020-02-03 16:35:41 +01:00
XenoLib.createContextMenuItem = (label, action, options = {}) => React.createElement(ContextMenuItem, { label, action: () => (!options.noClose && ContextMenuActions.closeContextMenu(), action()), ...options });
XenoLib.createContextMenuSubMenu = (label, items, options = {}) => React.createElement(ContextMenuSubMenuItem, { label, render: items, ...options });
2019-12-24 12:58:53 +01:00
XenoLib.createContextMenuGroup = (children, options) => React.createElement(ContextMenuItemsGroup, { children, ...options });
2020-02-03 16:35:41 +01:00
try {
XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('ButtonLink');
XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions.default;
} catch (e) {
Logger.stacktrace('Error getting Button component', e);
}
2019-12-24 12:58:53 +01:00
2020-03-01 19:26:58 +01:00
try {
const LinkClassname = XenoLib.joinClassNames(XenoLib.getClass('anchorUnderlineOnHover anchor'), XenoLib.getClass('anchor anchorUnderlineOnHover'), 'bda-author');
const handlePatch = (_this, _, ret) => {
2020-03-31 14:17:23 +02:00
if (!_this.props.addon || !_this.props.addon.plugin || typeof _this.props.addon.plugin.getAuthor().indexOf('Lighty') === -1) return;
const author = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('bda-author') !== -1);
if (!author || typeof author.props.children !== 'string' || author.props.children.indexOf('Lighty') === -1) return;
const onClick = () => {
if (DiscordAPI.currentUser.id === XenoLib.authorId) return;
PrivateChannelActions.ensurePrivateChannel(DiscordAPI.currentUser.id, XenoLib.authorId).then(() => {
PrivateChannelActions.openPrivateChannel(DiscordAPI.currentUser.id, XenoLib.authorId);
LayerManager.popLayer();
});
2020-03-01 19:26:58 +01:00
};
if (author.props.children === 'Lighty') {
author.type = 'a';
author.props.className = LinkClassname;
author.props.onClick = onClick;
} else {
const idx = author.props.children.indexOf('Lighty');
const pre = author.props.children.slice(0, idx);
const post = author.props.children.slice(idx + 6);
author.props.children = [
pre,
React.createElement(
'a',
{
className: LinkClassname,
onClick
},
'Lighty'
),
post
];
delete author.props.onClick;
author.props.className = 'bda-author';
author.type = 'span';
}
let footerProps = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('bda-links') !== -1);
if (!footerProps) return;
footerProps = footerProps.props;
if (!Array.isArray(footerProps.children)) footerProps.children = [footerProps.children];
const findLink = name => Utilities.findInReactTree(footerProps.children, e => e && e.props && e.props.children === name);
const websiteLink = findLink('Website');
const sourceLink = findLink('Source');
const supportServerLink = findLink('Support Server');
footerProps.children = [];
if (websiteLink) footerProps.children.push(websiteLink);
if (sourceLink) footerProps.children.push(websiteLink ? ' | ' : null, sourceLink);
footerProps.children.push(websiteLink || sourceLink ? ' | ' : null, React.createElement('a', { className: 'bda-link', onClick: e => ContextMenuActions.openContextMenu(e, e => React.createElement('div', { className: DiscordClasses.ContextMenu.contextMenu }, XenoLib.createContextMenuGroup([XenoLib.createContextMenuItem('Paypal', () => window.open('https://paypal.me/lighty13')), XenoLib.createContextMenuItem('Ko-fi', () => window.open('https://ko-fi.com/lighty_')), XenoLib.createContextMenuItem('Patreon', () => window.open('https://www.patreon.com/lightyp'))]))) }, 'Donate'));
footerProps.children.push(' | ', supportServerLink || React.createElement('a', { className: 'bda-link', onClick: () => (LayerManager.popLayer(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) }, 'Support Server'));
2020-03-31 01:40:55 +02:00
footerProps.children.push(' | ', React.createElement('a', { className: 'bda-link', onClick: () => (_this.props.addon.plugin.showChangelog ? _this.props.addon.plugin.showChangelog() : Modals.showChangelogModal(_this.props.addon.plugin.getName() + ' Changelog', _this.props.addon.plugin.getVersion(), _this.props.addon.plugin.getChanges())) }, 'Changelog'));
2020-03-31 14:17:23 +02:00
footerProps = null;
};
async function patchRewriteCard() {
/* nice try hiding it
adds extra buttons in BBD rewrite c:
*/
2020-03-31 14:17:23 +02:00
const component = [...ReactComponents.components.entries()].find(([_, e]) => e.component && e.component.prototype && e.component.prototype.reload && e.component.prototype.showSettings);
const AddonCard = component ? component[1] : await ReactComponents.getComponent('AddonCard', '.bda-slist > .ui-switch-item', e => e.prototype && e.prototype.reload && e.prototype.showSettings);
if (CancelledAsync) return;
/* *laughs in evil* */
2020-03-31 14:17:23 +02:00
Patcher.after(AddonCard.component.prototype, 'render', handlePatch, { displayName: AddonCard.id });
AddonCard.forceUpdateAll();
} /* I have a feeling I'm gonna get yelled at for doing this :eyes: */
2020-03-31 14:17:23 +02:00
patchRewriteCard();
2020-03-01 19:26:58 +01:00
} catch (e) {
Logger.stacktrace('Failed to patch V2C_*Card or AddonCard (BBD rewrite)', e);
2020-03-01 19:26:58 +01:00
}
2020-02-03 16:35:41 +01:00
/* shared between FilePicker and ColorPicker */
2020-02-27 21:05:23 +01:00
const MultiInputClassname = XenoLib.joinClassNames(Utilities.getNestedProp(DiscordClasses, 'BasicInputs.input.value'), XenoLib.getClass('multiInput'));
2019-12-24 12:58:53 +01:00
const MultiInputFirstClassname = XenoLib.getClass('multiInputFirst');
const MultiInputFieldClassname = XenoLib.getClass('multiInputField');
2020-03-31 14:17:23 +02:00
const ErrorMessageClassname = XenoLib.joinClassNames('xenoLib-error-text', XenoLib.getClass('errorMessage'), Utilities.getNestedProp(TextElement, 'Colors.RED'));
2019-12-24 12:58:53 +01:00
const ErrorClassname = XenoLib.getClass('input error');
2020-02-03 16:35:41 +01:00
try {
const dialog = require('electron').remote.dialog;
const DelayedCall = WebpackModules.getByProps('DelayedCall').DelayedCall;
const FsModule = require('fs');
/**
* @interface
* @name module:FilePicker
* @property {string} path
* @property {string} placeholder
* @property {Function} onChange
* @property {object} properties
* @property {bool} nullOnInvalid
2020-02-27 21:05:23 +01:00
* @property {bool} saveOnEnter
2020-02-03 16:35:41 +01:00
*/
XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
multiInputFocused: false,
path: props.path,
error: null
};
2020-02-27 21:05:23 +01:00
XenoLib._.bindAll(this, ['handleOnBrowse', 'handleChange', 'checkInvalidDir']);
this.handleKeyDown = XenoLib._.throttle(this.handleKeyDown.bind(this), 500);
this.delayedCallVerifyPath = new DelayedCall(500, () => this.checkInvalidDir());
}
checkInvalidDir(doSave) {
FsModule.access(this.state.path, FsModule.constants.W_OK, error => {
const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null;
this.setState({ error: invalid });
if (this.props.saveOnEnter && !doSave) return;
if (invalid) this.props.onChange(this.props.nullOnInvalid ? null : '');
2020-03-31 14:17:23 +02:00
else this.props.onChange(this.state.path);
2020-02-27 21:05:23 +01:00
});
2020-02-03 16:35:41 +01:00
}
handleOnBrowse() {
2020-02-27 21:05:23 +01:00
dialog.showOpenDialog({ title: this.props.title, properties: this.props.properties }).then(({ filePaths: [path] }) => {
if (path) this.handleChange(path);
});
2020-02-03 16:35:41 +01:00
}
handleChange(path) {
this.setState({ path });
this.delayedCallVerifyPath.delay();
}
2020-02-27 21:05:23 +01:00
handleKeyDown(e) {
if (!this.props.saveOnEnter || e.which !== DiscordConstants.KeyboardKeys.ENTER) return;
this.checkInvalidDir(true);
}
2020-02-03 16:35:41 +01:00
render() {
const n = {};
n[DiscordClasses.BasicInputs.focused] = this.state.multiInputFocused;
n[ErrorClassname] = !!this.state.error;
return React.createElement(
2019-12-24 12:58:53 +01:00
'div',
2020-02-03 16:35:41 +01:00
{ className: DiscordClasses.BasicInputs.inputWrapper, style: { width: '100%' } },
React.createElement(
'div',
{ className: XenoLib.joinClassNames(MultiInputClassname, n) },
React.createElement(DiscordModules.Textbox, {
value: this.state.path,
placeholder: this.props.placeholder,
onChange: this.handleChange,
onFocus: () => this.setState({ multiInputFocused: true }),
onBlur: () => this.setState({ multiInputFocused: false }),
2020-02-27 21:05:23 +01:00
onKeyDown: this.handleKeyDown,
2020-02-03 16:35:41 +01:00
autoFocus: false,
className: MultiInputFirstClassname,
inputClassName: MultiInputFieldClassname
}),
React.createElement(XenoLib.ReactComponents.Button, { onClick: this.handleOnBrowse, color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY, look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST, size: XenoLib.ReactComponents.Button.Sizes.MEDIUM }, 'Browse')
),
!!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error)
);
}
};
} catch (e) {
Logger.stacktrace('Failed to create FilePicker component', e);
}
2019-12-21 21:16:47 +01:00
2020-03-01 19:26:58 +01:00
/**
* @param {string} name - name label of the setting
* @param {string} note - help/note to show underneath or above the setting
* @param {string} value - current hex color
* @param {callable} onChange - callback to perform on setting change, callback receives hex string
* @param {object} [options] - object of options to give to the setting
* @param {boolean} [options.disabled=false] - should the setting be disabled
* @param {Array<number>} [options.colors=presetColors] - preset list of colors
* @author Zerebos, from his library ZLibrary
*/
const FormItem = WebpackModules.getByDisplayName('FormItem');
const DeprecatedModal = WebpackModules.getByDisplayName('DeprecatedModal');
const ModalContainerClassname = XenoLib.getClass('mobile container');
const ModalContentClassname = XenoLib.getClass('mobile container content');
2019-12-24 12:58:53 +01:00
2020-03-03 16:31:04 +01:00
const ColorPickerComponent = WebpackModules.getByDisplayName('ColorPicker');
2019-12-24 12:58:53 +01:00
2020-03-01 19:26:58 +01:00
class ColorPickerModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = { value: props.value };
XenoLib._.bindAll(this, ['handleChange']);
}
handleChange(value) {
this.setState({ value });
this.props.onChange(ColorConverter.int2hex(value));
}
render() {
return React.createElement(
DeprecatedModal,
{ className: ModalContainerClassname, tag: 'form', onSubmit: this.handleSubmit, size: '' },
React.createElement(
DeprecatedModal.Content,
{ className: ModalContentClassname },
2019-12-24 12:58:53 +01:00
React.createElement(
2020-03-01 19:26:58 +01:00
FormItem,
{ className: DiscordClasses.Margins.marginTop20 },
2020-03-03 16:31:04 +01:00
React.createElement(ColorPickerComponent, {
2020-03-01 19:26:58 +01:00
defaultColor: this.props.defaultColor,
colors: [16711680, 16746496, 16763904, 13434624, 65314, 65484, 61183, 43775, 26367, 8913151, 16711918, 16711782, 11730944, 11755264, 11767552, 9417472, 45848, 45967, 42931, 30643, 18355, 6226099, 11731111, 11731015],
value: this.state.value,
onChange: this.handleChange
})
2019-12-24 12:58:53 +01:00
)
2020-03-01 19:26:58 +01:00
)
);
2019-12-24 12:58:53 +01:00
}
2020-03-01 19:26:58 +01:00
}
2019-12-24 12:58:53 +01:00
2020-03-01 19:26:58 +01:00
const ExtraButtonClassname = XenoLib.joinClassNames('xenoLib-button', XenoLib.getClass('recording button'));
const TextClassname = XenoLib.getClass('recording text');
2020-03-03 16:31:04 +01:00
const DropperIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 16 16' }, React.createElement('path', { d: 'M14.994 1.006C13.858-.257 11.904-.3 10.72.89L8.637 2.975l-.696-.697-1.387 1.388 5.557 5.557 1.387-1.388-.697-.697 1.964-1.964c1.13-1.13 1.3-2.985.23-4.168zm-13.25 10.25c-.225.224-.408.48-.55.764L.02 14.37l1.39 1.39 2.35-1.174c.283-.14.54-.33.765-.55l4.808-4.808-2.776-2.776-4.813 4.803z', fill: 'currentColor' }));
const ClockReverseIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24' }, React.createElement('path', { d: 'M13,3 C8.03,3 4,7.03 4,12 L1,12 L4.89,15.89 L4.96,16.03 L9,12 L6,12 C6,8.13 9.13,5 13,5 C16.87,5 20,8.13 20,12 C20,15.87 16.87,19 13,19 C11.07,19 9.32,18.21 8.06,16.94 L6.64,18.36 C8.27,19.99 10.51,21 13,21 C17.97,21 22,16.97 22,12 C22,7.03 17.97,3 13,3 L13,3 Z M12,8 L12,13 L16.28,15.54 L17,14.33 L13.5,12.25 L13.5,8 L12,8 L12,8 Z', fill: 'currentColor' }));
2020-03-01 19:26:58 +01:00
class ColorPicker extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
error: null,
value: props.value,
multiInputFocused: false
};
XenoLib._.bindAll(this, ['handleChange', 'handleColorPicker', 'handleReset']);
}
handleChange(value) {
if (!value.length) {
this.state.error = 'You must input a hex string';
} else if (!ColorConverter.isValidHex(value)) {
this.state.error = 'Invalid hex string';
} else {
this.state.error = null;
2020-02-03 16:35:41 +01:00
}
2020-03-01 19:26:58 +01:00
this.setState({ value });
this.props.onChange(!value.length || !ColorConverter.isValidHex(value) ? this.props.defaultColor : value);
}
handleColorPicker() {
const modalId = ModalStack.push(e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'color picker modal', onError: () => ModalStack.popWithKey(modalId) }, React.createElement(ColorPickerModal, { ...e, defaultColor: ColorConverter.hex2int(this.props.defaultColor), value: ColorConverter.hex2int(this.props.value), onChange: this.handleChange })));
}
handleReset() {
this.handleChange(this.props.defaultColor);
}
render() {
const n = {};
n[DiscordClasses.BasicInputs.focused] = this.state.multiInputFocused;
n[ErrorClassname] = !!this.state.error;
return React.createElement(
'div',
{ className: XenoLib.joinClassNames(DiscordClasses.BasicInputs.inputWrapper.value, 'xenoLib-color-picker'), style: { width: '100%' } },
React.createElement(
2019-12-24 12:58:53 +01:00
'div',
2020-03-01 19:26:58 +01:00
{ className: XenoLib.joinClassNames(MultiInputClassname, n) },
React.createElement('div', {
className: XenoLib.ReactComponents.Button.Sizes.SMALL,
style: {
backgroundColor: this.state.value,
height: 38
}
}),
React.createElement(DiscordModules.Textbox, {
value: this.state.value,
placeholder: 'Hex color',
onChange: this.handleChange,
onFocus: () => this.setState({ multiInputFocused: true }),
onBlur: () => this.setState({ multiInputFocused: false }),
autoFocus: false,
className: MultiInputFirstClassname,
inputClassName: MultiInputFieldClassname
}),
2019-12-24 12:58:53 +01:00
React.createElement(
2020-03-01 19:26:58 +01:00
XenoLib.ReactComponents.Button,
{
onClick: this.handleColorPicker,
color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY,
look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST,
size: XenoLib.ReactComponents.Button.Sizes.MIN,
className: ExtraButtonClassname
},
React.createElement('span', { className: TextClassname }, 'Color picker'),
2019-12-24 12:58:53 +01:00
React.createElement(
2020-03-01 19:26:58 +01:00
'span',
2019-12-24 12:58:53 +01:00
{
2020-03-01 19:26:58 +01:00
className: 'xenoLib-button-icon'
2019-12-24 12:58:53 +01:00
},
2020-03-03 16:31:04 +01:00
DropperIcon
2020-03-01 19:26:58 +01:00
)
),
React.createElement(
XenoLib.ReactComponents.Button,
{
onClick: this.handleReset,
color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY,
look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST,
size: XenoLib.ReactComponents.Button.Sizes.MIN,
className: ExtraButtonClassname
},
React.createElement('span', { className: TextClassname }, 'Reset'),
2019-12-24 12:58:53 +01:00
React.createElement(
2020-03-01 19:26:58 +01:00
'span',
2019-12-24 12:58:53 +01:00
{
2020-03-01 19:26:58 +01:00
className: 'xenoLib-button-icon xenoLib-revert'
2019-12-24 12:58:53 +01:00
},
2020-03-03 16:31:04 +01:00
ClockReverseIcon
2019-12-24 12:58:53 +01:00
)
2020-03-01 19:26:58 +01:00
)
),
!!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error)
);
2019-12-24 12:58:53 +01:00
}
}
2020-02-27 21:05:23 +01:00
XenoLib.Settings = {};
XenoLib.Settings.FilePicker = class FilePickerSettingField extends Settings.SettingField {
constructor(name, note, value, onChange, options = { properties: ['openDirectory', 'createDirectory'], placeholder: 'Path to folder', defaultPath: '' }) {
super(name, note, onChange, XenoLib.ReactComponents.FilePicker || class b {}, {
onChange: reactElement => path => {
this.onChange(path ? path : options.defaultPath);
},
path: value,
nullOnInvalid: true,
...options
});
}
};
XenoLib.Settings.ColorPicker = class ColorPickerSettingField extends Settings.SettingField {
constructor(name, note, value, onChange, options = {}) {
super(name, note, onChange, ColorPicker, {
disabled: options.disabled ? true : false,
onChange: reactElement => color => {
this.onChange(color);
},
defaultColor: typeof options.defaultColor !== 'undefined' ? options.defaultColor : ColorConverter.int2hex(DiscordConstants.DEFAULT_ROLE_COLOR),
value
});
}
};
2019-12-24 12:58:53 +01:00
XenoLib.changeName = (currentName, newName) => {
try {
const path = require('path');
const fs = require('fs');
const pluginsFolder = path.dirname(currentName);
const pluginName = path.basename(currentName).match(/^[^\.]+/)[0];
if (pluginName === newName) return true;
2020-03-31 14:17:23 +02:00
const wasEnabled = BdApi.Plugins && BdApi.Plugins.isEnabled ? BdApi.Plugins.isEnabled(pluginName) : global.pluginCookie && pluginCookie[pluginName];
2019-12-24 12:58:53 +01:00
fs.accessSync(currentName, fs.constants.W_OK | fs.constants.R_OK);
const files = fs.readdirSync(pluginsFolder);
files.forEach(file => {
if (!file.startsWith(pluginName) || file.startsWith(newName) || file.indexOf('.plugin.js') !== -1) return;
fs.renameSync(path.resolve(pluginsFolder, file), path.resolve(pluginsFolder, `${newName}${file.match(new RegExp(`^${pluginName}(.*)`))[1]}`));
});
fs.renameSync(currentName, path.resolve(pluginsFolder, `${newName}.plugin.js`));
XenoLib.Notifications.success(`[**XenoLib**] \`${pluginName}\` file has been renamed to \`${newName}\``);
2020-03-31 14:17:23 +02:00
if ((!BdApi.Plugins || !BdApi.Plugins.isEnabled || !BdApi.Plugins.enable) && (!global.pluginCookie || !global.pluginModule)) Modals.showAlertModal('Plugin has been renamed', 'Plugin has been renamed, but your client mod has a missing feature, as such, the plugin could not be enabled (if it even was enabled).');
2019-12-24 12:58:53 +01:00
else {
if (!wasEnabled) return;
2020-03-31 14:17:23 +02:00
setTimeout(() => (BdApi.Plugins && BdApi.Plugins.enable ? BdApi.Plugins.enable(newName) : pluginModule.enablePlugin(newName)), 1000); /* /shrug */
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
} catch (e) {
Logger.stacktrace('There has been an issue renaming a plugin', e);
}
};
2019-12-21 21:16:47 +01:00
2020-03-01 19:26:58 +01:00
const FancyParser = (() => {
const ParsersModule = WebpackModules.getByProps('parseAllowLinks', 'parse');
try {
const DeepClone = WebpackModules.getByRegex(/function\(\w\)\{var \w=\{\},\w=\w,\w=Array\.isArray\(\w\),\w=0;for\(\w=\w\?\w:\w\[Symbol\.iterator\]\(\);;\)\{var \w;if\(\w\)\{\w/);
const ReactParserRules = WebpackModules.getByRegex(/function\(\){return \w}$/);
const FANCY_PANTS_PARSER_RULES = DeepClone([WebpackModules.getByProps('RULES', 'ALLOW_LINKS_RULES').ALLOW_LINKS_RULES, ReactParserRules()]);
FANCY_PANTS_PARSER_RULES.image = WebpackModules.getByProps('defaultParse').defaultRules.image;
return ParsersModule.reactParserFor(FANCY_PANTS_PARSER_RULES);
} catch (e) {
Logger.stacktrace('Failed to create special parser', e);
return ParsersModule.parseAllowLinks;
}
})();
const AnchorClasses = WebpackModules.getByProps('anchor', 'anchorUnderlineOnHover') || {};
const EmbedVideo = (() => {
try {
return WebpackModules.getByProps('EmbedVideo').EmbedVideo;
} catch (e) {
Logger.stacktrace('Failed to get EmbedVideo!', e);
return DiscordConstants.NOOP_NULL;
}
})();
const VideoComponent = (() => {
try {
const ret = new (WebpackModules.getByDisplayName('MediaPlayer'))({}).render();
const vc = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('video-8eMOth') !== -1);
return vc.type;
} catch (e) {
Logger.stacktrace('Failed to get the video component', e);
return DiscordConstants.NOOP_NULL;
}
})();
const ComponentRenderers = WebpackModules.getByProps('renderVideoComponent') || {};
/* MY CHANGELOG >:C */
XenoLib.showChangelog = (title, version, changelog, footer) => {
const ChangelogClasses = DiscordClasses.Changelog;
const items = [];
let isFistType = true;
for (let i = 0; i < changelog.length; i++) {
const item = changelog[i];
switch (item.type) {
case 'image':
items.push(React.createElement('img', { alt: '', src: item.src, width: item.width || 451, height: item.height || 254 }));
continue;
case 'video':
items.push(React.createElement(VideoComponent, { src: item.src, poster: item.thumbnail, width: item.width || 451, height: item.height || 254, loop: !0, muted: !0, autoPlay: !0, className: ChangelogClasses.video }));
continue;
case 'youtube':
items.push(React.createElement(EmbedVideo, { className: ChangelogClasses.video, allowFullScreen: !1, href: `https://youtu.be/${item.youtube_id}`, thumbnail: { url: `https://i.ytimg.com/vi/${item.youtube_id}/maxresdefault.jpg`, width: item.width || 451, height: item.height || 254 }, video: { url: `https://www.youtube.com/embed/${item.youtube_id}?vq=large&rel=0&controls=0&showinfo=0`, width: item.width || 451, height: item.height || 254 }, width: item.width || 451, height: item.height || 254, renderVideoComponent: ComponentRenderers.renderVideoComponent || DiscordConstants.NOOP_NULL, renderImageComponent: ComponentRenderers.renderImageComponent || DiscordConstants.NOOP_NULL, renderLinkComponent: ComponentRenderers.renderMaskedLinkComponent || DiscordConstants.NOOP_NULL }));
continue;
case 'description':
items.push(React.createElement('p', {}, FancyParser(item.content)));
continue;
default:
const logType = ChangelogClasses[item.type] || ChangelogClasses.added;
items.push(React.createElement('h1', { className: XenoLib.joinClassNames(logType.value, { [ChangelogClasses.marginTop.value]: item.marginTop || isFistType }) }, item.title));
items.push(
React.createElement(
'ul',
{ className: 'XL-chl-p' },
item.items.map(e =>
React.createElement(
'li',
{},
React.createElement(
'p',
{},
Array.isArray(e)
? e.map(e =>
Array.isArray(e)
? React.createElement(
'ul',
{},
e.map(e => React.createElement('li', {}, FancyParser(e)))
)
: FancyParser(e)
)
: FancyParser(e)
)
)
)
)
);
isFistType = false;
}
}
2020-03-03 16:33:39 +01:00
const renderFooter = () => ['Need support? ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => (LayerManager.popLayer(), ModalStack.pop(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) }, 'Join my support server'), FancyParser('! Or consider donating via [Paypal](https://paypal.me/lighty13), [Ko-fi](https://ko-fi.com/lighty_) or [Patreon](https://www.patreon.com/lightyp)!')];
2020-03-01 19:26:58 +01:00
ModalStack.push(props => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Changelog', onError: () => props.onClose() }, React.createElement(ChangelogModal, { className: ChangelogClasses.container, selectable: true, onScroll: _ => _, onClose: _ => _, renderHeader: () => React.createElement(FlexChild.Child, { grow: 1, shrink: 1 }, React.createElement(Titles.default, { tag: Titles.Tags.H4 }, title), React.createElement(TextElement.default, { size: TextElement.Sizes.SMALL, color: TextElement.Colors.PRIMARY, className: ChangelogClasses.date }, `Version ${version}`)), renderFooter: () => React.createElement(FlexChild.Child, { gro: 1, shrink: 1 }, React.createElement(TextElement.default, { size: TextElement.Sizes.SMALL, color: TextElement.Colors.PRIMARY }, footer ? (typeof footer === 'string' ? FancyParser(footer) : footer) : renderFooter())), children: items, ...props })));
};
2020-01-04 18:55:34 +01:00
/* NOTIFICATIONS START */
2020-03-01 19:26:58 +01:00
let UPDATEKEY = {};
2020-01-04 18:55:34 +01:00
try {
const DeepEqualityCheck = (content1, content2) => {
if (typeof content1 !== typeof content2) return false;
const isCNT1HTML = content1 instanceof HTMLElement;
const isCNT2HTML = content2 instanceof HTMLElement;
if (isCNT1HTML !== isCNT2HTML) return false;
else if (isCNT1HTML) return content1.isEqualNode(content2);
if (content1 !== content2) {
if (Array.isArray(content1)) {
if (content1.length !== content2.length) return false;
for (const [index, item] of content1.entries()) {
if (!DeepEqualityCheck(item, content2[index])) return false;
}
} else if (typeof content1 === 'object') {
if (content1.type) {
if (typeof content1.type !== typeof content2.type) return false;
if (content1.type !== content2.type) return false;
}
if (typeof content1.props !== typeof content2.props) return false;
if (content1.props) {
if (Object.keys(content1.props).length !== Object.keys(content2.props).length) return false;
for (const prop in content1.props) {
if (!DeepEqualityCheck(content1.props[prop], content2.props[prop])) return false;
}
}
} else return false;
}
return true;
};
2020-01-16 02:44:31 +01:00
const zustand = WebpackModules.getByRegex(/\w\(function\(\){return \w\(\w\)},\[\]\),\w\?\w:\w\.currentSlice},\w\]}/);
2020-01-04 18:55:34 +01:00
const [useStore, api] = zustand(e => ({ data: [] }));
const defaultOptions = {
loading: false,
progress: -1,
channelId: undefined,
2020-03-01 19:26:58 +01:00
timeout: 3500,
2020-03-02 17:48:40 +01:00
color: '#2196f3',
onLeave: DiscordConstants.NOOP
2020-01-04 18:55:34 +01:00
};
const utils = {
success(content, options = {}) {
return this.show(content, Object.assign({ color: '#43b581' }, options));
},
info(content, options = {}) {
return this.show(content, Object.assign({ color: '#4a90e2' }, options));
},
warning(content, options = {}) {
return this.show(content, Object.assign({ color: '#ffa600' }, options));
},
2020-01-09 20:16:13 +01:00
danger(content, options = {}) {
2020-01-04 18:55:34 +01:00
return this.show(content, Object.assign({ color: '#f04747' }, options));
},
error(content, options = {}) {
return this.danger(content, options);
},
/**
2020-03-02 17:48:40 +01:00
* @param {string|HTMLElement|React} content - Content to display. If it's a string, it'll be formatted with markdown, including URL support [like this](https://google.com/)
2020-01-04 18:55:34 +01:00
* @param {object} options
* @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example.
* @param {Number} [options.timeout] Set to 0 to keep it permanently until user closes it, or if you want a progress bar
* @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly
* @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically
* @param {string} [options.color] Bar color
* @param {string} [options.allowDuplicates] By default, notifications that are similar get grouped together, use true to disable that
2020-03-02 17:48:40 +01:00
* @param {function} [options.onLeave] Callback when notification is leaving
2020-01-04 18:55:34 +01:00
* @return {Number} - Notification ID. Store this if you plan on force closing it, changing its content or want to set the progress
*/
show(content, options = {}) {
let id = null;
2020-01-09 20:16:13 +01:00
options = Object.assign(Utilities.deepclone(defaultOptions), options);
2020-01-04 18:55:34 +01:00
api.setState(state => {
if (!options.allowDuplicates) {
const notif = state.data.find(n => DeepEqualityCheck(n.content, content) && n.timeout === options.timeout && !n.leaving);
2020-01-04 18:55:34 +01:00
if (notif) {
id = notif.id;
2020-01-28 20:58:28 +01:00
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_DUPLICATE', id: notif.id });
2020-01-04 18:55:34 +01:00
return state;
}
}
2020-01-06 12:06:15 +01:00
if (state.data.length >= 100) return state;
2020-01-04 18:55:34 +01:00
do {
id = Math.floor(4294967296 * Math.random());
} while (state.data.findIndex(n => n.id === id) !== -1);
2020-01-09 20:16:13 +01:00
return { data: [].concat(state.data, [{ content, ...options, id }]) };
2020-01-04 18:55:34 +01:00
});
return id;
},
remove(id) {
2020-01-28 20:58:28 +01:00
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_REMOVE', id });
2020-01-04 18:55:34 +01:00
},
/**
* @param {Number} id Notification ID
* @param {object} options
* @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example.
* @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly
* @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically
* @param {string} [options.color] Bar color
2020-03-02 17:48:40 +01:00
* @param {function} [options.onLeave] Callback when notification is leaving
2020-01-04 18:55:34 +01:00
*/
update(id, options) {
delete options.id;
api.setState(state => {
const idx = state.data.findIndex(n => n.id === id);
if (idx === -1) return state;
state.data[idx] = Object.assign(state.data[idx], options);
return state;
});
2020-01-28 20:58:28 +01:00
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_UPDATE', id, ...options });
2020-03-02 17:48:40 +01:00
},
exists(id) {
return api.getState().data.findIndex(e => e.id === id && !e.leaving) !== -1;
2020-01-04 18:55:34 +01:00
}
};
XenoLib.Notifications = utils;
const ReactSpring = WebpackModules.getByProps('useTransition');
const BadgesModule = WebpackModules.getByProps('NumberBadge');
2020-03-31 14:17:23 +02:00
const CloseButton = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24' }, React.createElement('path', { d: 'M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z', fill: 'currentColor' }));
2020-01-04 18:55:34 +01:00
class Notification extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
closeFast: false /* close button pressed, XL_NOTIFS_REMOVE dispatch */,
offscreen: false /* don't do anything special if offscreen, not timeout */,
counter: 1 /* how many times this notification was shown */,
resetBar: false /* reset bar to 0 in the event counter goes up */,
hovered: false,
leaving: true /* prevent hover events from messing up things */,
loading: props.loading /* loading animation, enable progress */,
progress: props.progress /* -1 means undetermined */,
content: props.content,
contentParsed: this.parseContent(props.content, props.channelId),
color: props.color
};
this._contentRef = null;
2020-03-31 14:17:23 +02:00
this._ref = null;
this._animationCancel = DiscordConstants.NOOP;
2020-01-04 18:55:34 +01:00
this._oldOffsetHeight = 0;
this._initialProgress = !this.props.timeout ? (this.state.loading && this.state.progress !== -1 ? this.state.progress : 100) : 0;
2020-03-01 19:26:58 +01:00
XenoLib._.bindAll(this, ['closeNow', 'handleDispatch', '_setContentRef']);
this.handleResizeEvent = XenoLib._.throttle(this.handleResizeEvent.bind(this), 100);
this.resizeObserver = new ResizeObserver(this.handleResizeEvent);
this._timeout = props.timeout;
2020-01-04 18:55:34 +01:00
}
componentDidMount() {
this._unsubscribe = api.subscribe(_ => this.checkOffScreen());
window.addEventListener('resize', this.handleResizeEvent);
Dispatcher.subscribe('XL_NOTIFS_DUPLICATE', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_REMOVE', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_UPDATE', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_ANIMATED', this.handleDispatch);
2020-03-01 19:26:58 +01:00
Dispatcher.subscribe('XL_NOTIFS_SETTINGS_UPDATE', this.handleDispatch);
2020-01-04 18:55:34 +01:00
}
componentWillUnmount() {
this._unsubscribe();
window.window.removeEventListener('resize', this.handleResizeEvent);
Dispatcher.unsubscribe('XL_NOTIFS_DUPLICATE', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_REMOVE', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_UPDATE', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_ANIMATED', this.handleDispatch);
2020-03-01 19:26:58 +01:00
Dispatcher.unsubscribe('XL_NOTIFS_SETTINGS_UPDATE', this.handleDispatch);
this.resizeObserver.disconnect();
2020-03-31 14:17:23 +02:00
this.resizeObserver = null; /* no mem leaks plz */
this._ref = null;
this._contentRef = null;
2020-01-04 18:55:34 +01:00
}
handleDispatch(e) {
2020-03-12 19:53:45 +01:00
if (this.state.leaving || this.state.closeFast) return;
2020-03-01 19:26:58 +01:00
if (e.type === 'XL_NOTIFS_SETTINGS_UPDATE') {
if (e.key !== UPDATEKEY) return;
this._animationCancel();
this.forceUpdate();
return;
}
2020-01-04 18:55:34 +01:00
if (e.type === 'XL_NOTIFS_ANIMATED') this.checkOffScreen();
if (e.id !== this.props.id) return;
const { content, channelId, loading, progress, color } = e;
const { content: curContent, channelId: curChannelId, loading: curLoading, progress: curProgress, color: curColor } = this.state;
switch (e.type) {
case 'XL_NOTIFS_REMOVE':
this.closeNow();
break;
case 'XL_NOTIFS_DUPLICATE':
this._animationCancel();
2020-01-06 12:06:15 +01:00
this.setState({ counter: this.state.counter + 1, resetBar: !!this.props.timeout, closeFast: false });
2020-01-04 18:55:34 +01:00
break;
case 'XL_NOTIFS_UPDATE':
2020-03-31 14:17:23 +02:00
if (!this.state.initialAnimDone) {
this.state.content = content || curContent;
this.state.channelId = channelId || curChannelId;
this.state.contentParsed = this.parseContent(content || curContent, channelId || curChannelId);
if (typeof loading !== 'undefined') this.state.loading = loading;
if (typeof progress !== 'undefined') this.state.progress = progress;
this.state.color = color || curColor;
return;
}
2020-01-04 18:55:34 +01:00
this._animationCancel();
this.setState({
content: content || curContent,
channelId: channelId || curChannelId,
contentParsed: this.parseContent(content || curContent, channelId || curChannelId),
loading: typeof loading !== 'undefined' ? loading : curLoading,
progress: typeof progress !== 'undefined' ? progress : curProgress,
color: color || curColor
});
break;
}
}
parseContent(content, channelId) {
2020-03-01 19:26:58 +01:00
if (typeof content === 'string') return FancyParser(content, true, { channelId });
else if (content instanceof Element) return ReactTools.createWrappedElement(content);
else return content;
2020-01-04 18:55:34 +01:00
}
checkOffScreen() {
2020-03-31 14:17:23 +02:00
if (this.state.leaving || !this._contentRef) return;
2020-01-04 18:55:34 +01:00
const bcr = this._contentRef.getBoundingClientRect();
2020-01-06 12:06:15 +01:00
if (bcr.bottom > Structs.Screen.height || bcr.top < 0) {
2020-01-04 18:55:34 +01:00
if (!this.state.offscreen) {
this._animationCancel();
this.setState({ offscreen: true });
}
} else if (this.state.offscreen) {
this._animationCancel();
this.setState({ offscreen: false });
}
}
closeNow() {
2020-01-06 20:51:57 +01:00
if (this.state.closeFast) return;
2020-03-01 19:26:58 +01:00
this.resizeObserver.disconnect();
2020-01-04 18:55:34 +01:00
this._animationCancel();
2020-03-12 19:53:45 +01:00
api.setState(state => {
const dt = state.data.find(m => m.id === this.props.id);
if (dt) dt.leaving = true;
return { data: state.data };
});
2020-01-04 18:55:34 +01:00
this.setState({ closeFast: true });
}
handleResizeEvent() {
if (this._oldOffsetHeight !== this._contentRef.offsetHeight) {
this._animationCancel();
this.forceUpdate();
}
}
2020-03-01 19:26:58 +01:00
_setContentRef(ref) {
if (!ref) return;
this._contentRef = ref;
this.resizeObserver.observe(ref);
}
2020-01-04 18:55:34 +01:00
render() {
2020-01-06 12:06:15 +01:00
const config = { duration: 200 };
2020-01-04 18:55:34 +01:00
if (this._contentRef) this._oldOffsetHeight = this._contentRef.offsetHeight;
return React.createElement(
ReactSpring.Spring,
{
native: true,
from: { opacity: 0, height: 0, progress: this._initialProgress, loadbrightness: 1 },
to: async (next, cancel) => {
this.state.leaving = false;
this._animationCancel = cancel;
if (this.state.offscreen) {
if (this.state.closeFast) {
this.state.leaving = true;
await next({ opacity: 0, height: 0 });
api.setState(state => ({ data: state.data.filter(n => n.id !== this.props.id) }));
return;
}
await next({ opacity: 1, height: this._contentRef.offsetHeight, loadbrightness: 1 });
if (this.props.timeout) {
await next({ progress: 0 });
} else {
if (this.state.loading && this.state.progress !== -1) {
await next({ progress: 0 });
} else {
await next({ progress: 100 });
}
}
return;
}
const isSettingHeight = this._ref.offsetHeight !== this._contentRef.offsetHeight;
await next({ opacity: 1, height: this._contentRef.offsetHeight });
2020-01-28 20:58:28 +01:00
if (isSettingHeight) Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_ANIMATED' });
2020-03-01 19:26:58 +01:00
this.state.initialAnimDone = true;
if (this.state.resetBar || (this.state.hovered && LibrarySettings.notifications.timeoutReset)) {
2020-01-04 18:55:34 +01:00
await next({ progress: 0 }); /* shit gets reset */
this.state.resetBar = false;
}
if (!this.props.timeout && !this.state.closeFast) {
if (!this.state.loading) {
await next({ progress: 100 });
} else {
await next({ loadbrightness: 1 });
if (this.state.progress === -1) await next({ progress: 100 });
else await next({ progress: this.state.progress });
}
2020-03-12 19:53:45 +01:00
if (this.state.progress < 100 || !this.state.loading) return;
2020-01-04 18:55:34 +01:00
}
if (this.state.hovered && !this.state.closeFast) return;
2020-03-01 19:26:58 +01:00
if (!this.state.closeFast && !LibrarySettings.notifications.timeoutReset) this._startProgressing = Date.now();
2020-01-04 18:55:34 +01:00
await next({ progress: 100 });
this.state.leaving = true;
2020-03-12 19:53:45 +01:00
if (!this.state.closeFast) {
api.setState(state => {
const dt = state.data.find(m => m.id === this.props.id);
if (dt) dt.leaving = true;
return { data: state.data };
});
}
2020-03-02 17:48:40 +01:00
this.props.onLeave();
2020-01-04 18:55:34 +01:00
await next({ opacity: 0, height: 0 });
api.setState(state => ({ data: state.data.filter(n => n.id !== this.props.id) }));
},
config: key => {
if (key === 'progress') {
2020-03-01 19:26:58 +01:00
let duration = this._timeout;
2020-01-04 18:55:34 +01:00
if (this.state.closeFast || !this.props.timeout || this.state.resetBar || this.state.hovered) duration = 150;
if (this.state.offscreen) duration = 0; /* don't animate at all */
return { duration };
}
if (key === 'loadbrightness') return { duration: 750 };
return config;
}
},
e => {
return React.createElement(
ReactSpring.animated.div,
{
style: {
height: e.height,
opacity: e.opacity
},
className: 'xenoLib-notification',
ref: e => e && (this._ref = e)
},
React.createElement(
'div',
{
className: 'xenoLib-notification-content-wrapper',
2020-03-01 19:26:58 +01:00
ref: this._setContentRef,
2020-01-04 18:55:34 +01:00
onMouseEnter: e => {
2020-01-06 20:51:57 +01:00
if (this.state.leaving || !this.props.timeout || this.state.closeFast) return;
2020-01-04 18:55:34 +01:00
this._animationCancel();
2020-03-01 19:26:58 +01:00
if (this._startProgressing) {
this._timeout -= Date.now() - this._startProgressing;
}
2020-01-04 18:55:34 +01:00
this.setState({ hovered: true });
},
onMouseLeave: e => {
2020-03-31 14:17:23 +02:00
if (this.state.leaving || !this.props.timeout || this.state.closeFast) return;
2020-01-04 18:55:34 +01:00
this._animationCancel();
this.setState({ hovered: false });
},
style: {
'--grad-one': this.state.color,
'--grad-two': ColorConverter.lightenColor(this.state.color, 20),
'--bar-color': ColorConverter.darkenColor(this.state.color, 30)
},
onClick: e => {
if (!this.props.onClick) return;
2020-01-28 20:58:28 +01:00
if (e.target && e.target.getAttribute('role') === 'button') return;
2020-01-04 18:55:34 +01:00
this.props.onClick();
this.closeNow();
},
onContextMenu: e => {
if (!this.props.onContext) return;
this.props.onContext();
this.closeNow();
}
},
React.createElement(
'div',
{
2020-03-01 19:26:58 +01:00
className: 'xenoLib-notification-content',
style: LibrarySettings.notifications.backdrop
? {
backdropFilter: 'blur(5px)',
background: ColorConverter.int2rgba(ColorConverter.hex2int(LibrarySettings.notifications.backdropColor), 0.3),
border: 'none'
}
: undefined,
ref: e => {
if (!LibrarySettings.notifications.backdrop || !e) return;
e.style.setProperty('backdrop-filter', e.style.backdropFilter, 'important');
e.style.setProperty('background', e.style.background, 'important');
e.style.setProperty('border', e.style.border, 'important');
}
2020-01-04 18:55:34 +01:00
},
React.createElement(ReactSpring.animated.div, {
2020-01-06 12:06:15 +01:00
className: XenoLib.joinClassNames('xenoLib-notification-loadbar', { 'xenoLib-notification-loadbar-striped': !this.props.timeout && this.state.loading, 'xenoLib-notification-loadbar-user': !this.props.timeout && !this.state.loading }),
2020-01-04 18:55:34 +01:00
style: { right: e.progress.to(e => 100 - e + '%'), filter: e.loadbrightness.to(e => `brightness(${e * 100}%)`) }
}),
2020-03-31 14:17:23 +02:00
React.createElement(
XenoLib.ReactComponents.Button,
{
look: XenoLib.ReactComponents.Button.Looks.BLANK,
size: XenoLib.ReactComponents.Button.Sizes.NONE,
onClick: e => {
e.preventDefault();
e.stopPropagation();
this.closeNow();
},
onContextMenu: e => {
const state = api.getState();
state.data.forEach(notif => utils.remove(notif.id));
},
className: 'xenoLib-notification-close'
2020-01-04 18:55:34 +01:00
},
2020-03-31 14:17:23 +02:00
CloseButton
),
2020-01-04 18:55:34 +01:00
this.state.counter > 1 && BadgesModule.NumberBadge({ count: this.state.counter, className: 'xenLib-notification-counter', color: '#2196f3' }),
this.state.contentParsed
)
)
);
}
);
}
}
function NotificationsWrapper(e) {
const notifications = useStore(e => {
return e.data;
});
2020-03-31 14:17:23 +02:00
return notifications.map(item => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: `Notification ${item.id}`, onError: () => api.setState(state => ({ data: state.data.filter(n => n.id !== item.id) })), key: item.id.toString() }, React.createElement(Notification, { ...item, leaving: false }))).reverse();
2020-01-04 18:55:34 +01:00
}
NotificationsWrapper.displayName = 'XenoLibNotifications';
const DOMElement = document.createElement('div');
2020-01-06 12:06:15 +01:00
DOMElement.className = XenoLib.joinClassNames('xenoLib-notifications', `xenoLib-centering-${LibrarySettings.notifications.position}`);
2020-01-04 18:55:34 +01:00
ReactDOM.render(React.createElement(NotificationsWrapper, {}), DOMElement);
document.querySelector('#app-mount').appendChild(DOMElement);
} catch (e) {
2020-01-16 02:44:31 +01:00
Logger.stacktrace('There has been an error loading the Notifications system, fallback object has been put in place to prevent errors', e);
XenoLib.Notifications = {
2020-01-16 02:44:31 +01:00
success(content, options = {}) {},
info(content, options = {}) {},
warning(content, options = {}) {},
danger(content, options = {}) {},
error(content, options = {}) {},
show(content, options = {}) {},
remove(id) {},
update(id, options) {}
};
2020-01-04 18:55:34 +01:00
}
/* NOTIFICATIONS END */
2019-12-24 12:58:53 +01:00
global.XenoLib = XenoLib;
2020-01-06 12:06:15 +01:00
const notifLocations = ['topLeft', 'topMiddle', 'topRight', 'bottomLeft', 'bottomMiddle', 'bottomRight'];
2020-02-03 16:35:41 +01:00
const notifLocationClasses = [`${XenoLib.getClass('selected topLeft')} ${XenoLib.getClass('topLeft option')}`, `topMiddle-xenoLib ${XenoLib.getClass('topLeft option')}`, `${XenoLib.getClass('selected topRight')} ${XenoLib.getClass('topLeft option')}`, `${XenoLib.getClass('selected bottomLeft')} ${XenoLib.getClass('topLeft option')}`, `bottomMiddle-xenoLib ${XenoLib.getClass('topLeft option')}`, `${XenoLib.getClass('selected bottomRight')} ${XenoLib.getClass('topLeft option')}`];
2020-01-06 12:06:15 +01:00
const PositionSelectorWrapperClassname = XenoLib.getClass('topLeft wrapper');
const PositionSelectorSelectedClassname = XenoLib.getClass('topLeft selected');
const PositionSelectorHiddenInputClassname = XenoLib.getClass('topLeft hiddenInput');
const FormText = WebpackModules.getByDisplayName('FormText');
2020-01-04 18:55:34 +01:00
class NotificationPosition extends React.PureComponent {
2020-01-06 12:06:15 +01:00
constructor(props) {
super(props);
this.state = {
position: props.position
};
}
componentDidMount() {
this._notificationId = XenoLib.Notifications.show('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lacinia justo eget libero ultrices mollis.', { timeout: 0 });
}
componentWillUnmount() {
XenoLib.Notifications.remove(this._notificationId);
}
getSelected() {
switch (this.state.position) {
case 'topLeft':
return 'Top Left';
case 'topMiddle':
return 'Top Middle';
case 'topRight':
return 'Top Right';
case 'bottomLeft':
return 'Bottom Left';
case 'bottomMiddle':
return 'Bottom Middle';
case 'bottomRight':
return 'Bottom Right';
default:
return 'Unknown';
}
}
2020-01-04 18:55:34 +01:00
render() {
2020-01-06 12:06:15 +01:00
return React.createElement(
'div',
{},
React.createElement(
'div',
{
className: PositionSelectorWrapperClassname
},
notifLocations.map((e, i) => {
return React.createElement(
'label',
{
className: XenoLib.joinClassNames(notifLocationClasses[i], { [PositionSelectorSelectedClassname]: this.state.position === e })
},
React.createElement('input', {
type: 'radio',
name: 'xenolib-notif-position-selector',
value: e,
onChange: () => {
this.props.onChange(e);
this.setState({ position: e });
},
className: PositionSelectorHiddenInputClassname
})
);
})
),
React.createElement(
FormText,
{
type: FormText.Types.DESCRIPTION,
className: DiscordClasses.Margins.marginTop8
},
this.getSelected()
)
);
2020-01-04 18:55:34 +01:00
}
2020-01-06 12:06:15 +01:00
}
2020-01-04 18:55:34 +01:00
class NotificationPositionField extends Settings.SettingField {
2020-01-06 12:06:15 +01:00
constructor(name, note, onChange, value) {
super(name, note, onChange, NotificationPosition, {
position: value,
onChange: reactElement => position => {
this.onChange(position);
}
2020-01-04 18:55:34 +01:00
});
}
}
2019-12-21 21:16:47 +01:00
2020-01-06 12:06:15 +01:00
return class CXenoLib extends Plugin {
constructor() {
super();
this.settings = LibrarySettings;
2020-02-27 21:05:23 +01:00
XenoLib.changeName(__filename, '1XenoLib'); /* prevent user from changing libs filename */
try {
ModalStack.popWithKey(`${this.name}_DEP_MODAL`);
} catch (e) {}
2020-02-18 14:51:31 +01:00
}
load() {
2020-02-19 16:32:24 +01:00
super.load();
2020-03-31 14:17:23 +02:00
if (!BdApi.Plugins) return; /* well shit what now */
if (!BdApi.isSettingEnabled || !BdApi.disableSetting) return;
const prev = BdApi.isSettingEnabled('fork-ps-2');
if (prev) BdApi.disableSetting('fork-ps-2');
const list = BdApi.Plugins.getAll().filter(k => k._XL_PLUGIN);
for (let p = 0; p < list.length; p++) {
try {
2020-03-31 14:17:23 +02:00
BdApi.Plugins.reload(list[p].getName());
} catch (e) {
2020-03-31 14:17:23 +02:00
try {
Logger.stacktrace(`Failed to reload plugin ${list[p].getName()}`, e);
} catch (e) {
Logger.error(`Failed telling you about failing to reload a plugin`, list[p], e);
}
}
2020-02-18 14:51:31 +01:00
}
2020-03-31 14:17:23 +02:00
if (prev) BdApi.enableSetting('fork-ps-2');
2020-01-06 12:06:15 +01:00
}
buildSetting(data) {
2020-01-04 18:55:34 +01:00
if (data.type === 'position') {
2020-01-06 12:06:15 +01:00
const setting = new NotificationPositionField(data.name, data.note, data.onChange, data.value);
if (data.id) setting.id = data.id;
return setting;
2020-03-01 19:26:58 +01:00
} else if (data.type === 'color') {
const setting = new XenoLib.Settings.ColorPicker(data.name, data.note, data.value, data.onChange, data.options);
if (data.id) setting.id = data.id;
return setting;
2020-01-04 18:55:34 +01:00
}
return super.buildSetting(data);
2019-12-24 12:58:53 +01:00
}
2020-01-04 18:55:34 +01:00
getSettingsPanel() {
return this.buildSettingsPanel().getElement();
2020-01-06 12:06:15 +01:00
}
saveSettings(category, setting, value) {
2020-01-06 20:51:57 +01:00
this.settings[category][setting] = value;
2020-01-06 12:06:15 +01:00
LibrarySettings[category][setting] = value;
PluginUtilities.saveSettings(this.name, LibrarySettings);
if (category === 'notifications') {
if (setting === 'position') {
const DOMElement = document.querySelector('.xenoLib-notifications');
if (DOMElement) {
DOMElement.className = XenoLib.joinClassNames('xenoLib-notifications', `xenoLib-centering-${LibrarySettings.notifications.position}`);
2020-01-28 20:58:28 +01:00
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_ANIMATED' });
2020-01-06 12:06:15 +01:00
}
2020-03-01 19:26:58 +01:00
} else if (setting === 'backdrop' || setting === 'backdropColor') {
Dispatcher.wait(() => Dispatcher.dispatch({ type: 'XL_NOTIFS_SETTINGS_UPDATE', key: UPDATEKEY }), (UPDATEKEY = {}));
2020-01-06 12:06:15 +01:00
}
}
}
2020-02-27 21:05:23 +01:00
showChangelog(footer) {
2020-03-01 19:26:58 +01:00
XenoLib.showChangelog(`${this.name} has been updated!`, this.version, this._config.changelog);
2020-02-27 21:05:23 +01:00
}
2019-12-24 12:58:53 +01:00
get name() {
return config.info.name;
}
get short() {
let string = '';
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
for (let i = 0, len = config.info.name.length; i < len; i++) {
const char = config.info.name[i];
if (char === char.toUpperCase()) string += char;
}
return string;
}
get author() {
return config.info.authors.map(author => author.name).join(', ');
}
get version() {
return config.info.version;
}
get description() {
return config.info.description;
}
2019-12-21 21:16:47 +01:00
};
2019-12-24 12:58:53 +01:00
};
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
/* Finalize */
2019-12-21 21:16:47 +01:00
2020-02-27 21:05:23 +01:00
let ZeresPluginLibraryOutdated = false;
try {
if (global.BdApi && 'function' == typeof BdApi.getPlugin) {
const a = (c, a) => ((c = c.split('.').map(b => parseInt(b))), (a = a.split('.').map(b => parseInt(b))), !!(a[0] > c[0])) || !!(a[0] == c[0] && a[1] > c[1]) || !!(a[0] == c[0] && a[1] == c[1] && a[2] > c[2]),
b = BdApi.getPlugin('ZeresPluginLibrary');
2020-03-31 14:17:23 +02:00
((b, c) => b && b._config && b._config.info && b._config.info.version && a(b._config.info.version, c))(b, '1.2.14') && (ZeresPluginLibraryOutdated = !0);
2020-02-27 21:05:23 +01:00
}
} catch (e) {
console.error('Error checking if ZeresPluginLibrary is out of date', e);
}
return !global.ZeresPluginLibrary || ZeresPluginLibraryOutdated
2019-12-24 12:58:53 +01:00
? class {
constructor() {
this._config = config;
2020-03-31 14:17:23 +02:00
this.start = this.load = this.handleMissingLib;
}
2019-12-24 12:58:53 +01:00
getName() {
return this.name.replace(/\s+/g, '');
}
getAuthor() {
return this.author;
}
getVersion() {
return this.version;
}
getDescription() {
2020-03-31 14:17:23 +02:00
return this.description + ' You are missing ZeresPluginLibrary for this plugin, please enable the plugin and click Download Now.';
2019-12-24 12:58:53 +01:00
}
stop() {}
2020-03-31 14:17:23 +02:00
handleMissingLib() {
const a = BdApi.findModuleByProps('isModalOpenWithKey');
if (a && a.isModalOpenWithKey(`${this.name}_DEP_MODAL`)) return;
const b = !global.ZeresPluginLibrary,
c = ZeresPluginLibraryOutdated ? 'Outdated Library' : 'Missing Library',
d = `The Library ZeresPluginLibrary required for ${this.name} is ${ZeresPluginLibraryOutdated ? 'outdated' : 'missing'}.`,
e = BdApi.findModuleByProps('push', 'update', 'pop', 'popWithKey'),
f = BdApi.findModuleByProps('Sizes', 'Weights'),
g = BdApi.findModule(a => a.defaultProps && a.key && 'confirm-modal' === a.key()),
2020-03-31 14:17:23 +02:00
h = () => BdApi.alert(c, BdApi.React.createElement('span', {}, BdApi.React.createElement('div', {}, d), `Due to a slight mishap however, you'll have to download the libraries yourself.`, b || ZeresPluginLibraryOutdated ? BdApi.React.createElement('div', {}, BdApi.React.createElement('a', { href: 'https://betterdiscord.net/ghdl?id=2252', target: '_blank' }, 'Click here to download ZeresPluginLibrary')) : null));
if (!e || !g || !f) return h();
class i extends BdApi.React.PureComponent {
2020-02-27 21:05:23 +01:00
constructor(a) {
super(a), (this.state = { hasError: !1 });
2020-02-03 16:35:41 +01:00
}
componentDidCatch(a) {
2020-02-27 21:05:23 +01:00
console.error(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`), this.setState({ hasError: !0 }), 'function' == typeof this.props.onError && this.props.onError(a);
2020-02-03 16:35:41 +01:00
}
render() {
2020-02-27 21:05:23 +01:00
return this.state.hasError ? null : this.props.children;
2020-02-03 16:35:41 +01:00
}
}
class j extends g {
submitModal() {
this.props.onConfirm();
}
}
let k = !1;
const l = e.push(
a =>
2020-02-03 16:35:41 +01:00
BdApi.React.createElement(
i,
{
label: 'missing dependency modal',
onError: () => {
e.popWithKey(l), h();
}
},
BdApi.React.createElement(
j,
Object.assign(
{
header: c,
children: [BdApi.React.createElement(f, { color: f.Colors.PRIMARY, children: [`${d} Please click Download Now to download it.`] })],
red: !1,
confirmText: 'Download Now',
cancelText: 'Cancel',
onConfirm: () => {
if (k) return;
k = !0;
const a = require('request'),
b = require('fs'),
c = require('path');
2020-03-31 14:17:23 +02:00
a('https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js', (a, d, f) => (a || 200 !== d.statusCode ? (e.popWithKey(l), h()) : void b.writeFile(c.join(BdApi.Plugins.folder, '0PluginLibrary.plugin.js'), f, () => {})));
}
},
a
)
2020-02-03 16:35:41 +01:00
)
),
void 0,
`${this.name}_DEP_MODAL`
2020-02-27 21:05:23 +01:00
);
2019-12-24 12:58:53 +01:00
}
get name() {
return config.info.name;
}
get short() {
let string = '';
for (let i = 0, len = config.info.name.length; i < len; i++) {
const char = config.info.name[i];
if (char === char.toUpperCase()) string += char;
}
return string;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
get author() {
return config.info.authors.map(author => author.name).join(', ');
}
get version() {
return config.info.version;
}
get description() {
return config.info.description;
}
}
: buildPlugin(global.ZeresPluginLibrary.buildPlugin(config));
2019-12-21 21:16:47 +01:00
})();
/*@end@*/