XL v1.3.8 fix startup issues

This commit is contained in:
_Lighty_ 2020-02-03 16:35:41 +01:00
parent 809d599321
commit 23bad1ff10
1 changed files with 416 additions and 337 deletions

View File

@ -41,7 +41,7 @@ var XenoLib = (() => {
twitter_username: '' twitter_username: ''
} }
], ],
version: '1.3.7', version: '1.3.8',
description: 'Simple library to complement plugins with shared code without lowering performance.', description: 'Simple library to complement plugins with shared code without lowering performance.',
github: 'https://github.com/1Lighty', github: 'https://github.com/1Lighty',
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js' github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js'
@ -50,7 +50,7 @@ var XenoLib = (() => {
{ {
title: 'Boring changes', title: 'Boring changes',
type: 'Added', type: 'Added',
items: ['Fixed crash if you deleted Zeres lib'] items: ['Fixed error loading, if BDFDB exists. This is so sad, can we pat Lighty?', 'Should no longer throw "Cannot read property \'toString\' of undefined", nor fail to load under most circumstances.', 'Improved missing lib warning. It is no longer is possible for it to crash Discord, if another plugin loads Zeres lib, XenoLib auto closes its own modal and reload itself, so it can start functioning. This will cause a chain reaction of plugins starting to function, so you don\'t get spammed with "Missing library" modals.']
} }
], ],
defaultConfig: [ defaultConfig: [
@ -92,8 +92,6 @@ var XenoLib = (() => {
} }
} }
const ContextMenuSubMenuItem = WebpackModules.getByDisplayName('FluxContainer(SubMenuItem)');
if (global.XenoLib) global.XenoLib.shutdown(); if (global.XenoLib) global.XenoLib.shutdown();
const XenoLib = {}; const XenoLib = {};
XenoLib.shutdown = () => { XenoLib.shutdown = () => {
@ -126,10 +124,30 @@ var XenoLib = (() => {
}; };
XenoLib.getClass = arg => { XenoLib.getClass = arg => {
const args = arg.split(' '); try {
return WebpackModules.getByProps(...args)[args[args.length - 1]]; const args = arg.split(' ');
return WebpackModules.getByProps(...args)[args[args.length - 1]];
} catch (e) {
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 '';
}
}; };
XenoLib.getSingleClass = arg => XenoLib.getClass(arg).split(' ')[0]; XenoLib.getSingleClass = arg => {
try {
return XenoLib.getClass(arg).split(' ')[0];
} catch (e) {
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 = {};
const LibrarySettings = XenoLib.loadData(config.info.name, 'settings', DefaultLibrarySettings); const LibrarySettings = XenoLib.loadData(config.info.name, 'settings', DefaultLibrarySettings);
@ -286,19 +304,22 @@ var XenoLib = (() => {
} }
` `
); );
XenoLib.getUser = WebpackModules.getByProps('getUser', 'acceptAgreements').getUser;
XenoLib.joinClassNames = WebpackModules.getModule(e => e.default && e.default.default); XenoLib.joinClassNames = WebpackModules.getModule(e => e.default && e.default.default);
XenoLib.authorId = '239513071272329217'; XenoLib.authorId = '239513071272329217';
const requestUser = () =>
XenoLib.getUser(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();
XenoLib.supportServerId = '389049952732446731'; XenoLib.supportServerId = '389049952732446731';
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);
}
try { try {
/* const pluginAuthors = [ /* const pluginAuthors = [
{ {
@ -492,33 +513,51 @@ var XenoLib = (() => {
Patcher.after(V2C_PluginCard.prototype, 'render', handlePatch); Patcher.after(V2C_PluginCard.prototype, 'render', handlePatch);
Patcher.after(V2C_ThemeCard.prototype, 'render', handlePatch); Patcher.after(V2C_ThemeCard.prototype, 'render', handlePatch);
} }
} catch (e) {} } catch (e) {
Logger.stacktrace('Failed to patch V2C_*Card', e);
}
XenoLib.ReactComponents = {};
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;
}
};
XenoLib.__contextPatches = []; XenoLib.__contextPatches = [];
XenoLib.__contextPatches.__contextPatched = false; try {
const existingContextMenus = ['NativeContextMenu', 'GuildRoleContextMenu', 'MessageContextMenu', 'DeveloperContextMenu', 'ScreenshareContextMenu']; if (global.XenoLib) if (global.XenoLib.__contextPatches && global.XenoLib.__contextPatches.length) XenoLib.__contextPatches.push(...global.XenoLib.__contextPatches);
const ContextMenuClassname = XenoLib.getSingleClass('subMenuContext contextMenu'); const ContextMenuClassname = XenoLib.getSingleClass('subMenuContext contextMenu'); /* I AM *SPEED* */
const getContextMenuChild = val => { const getContextMenuChild = val => {
if (!val) return; 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')); 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; if (isValid(val)) return val.props.children;
const children = Utilities.getNestedProp(val, 'props.children'); const children = Utilities.getNestedProp(val, 'props.children');
if (!children) return; if (!children) return;
if (Array.isArray(children)) { if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const ret = getContextMenuChild(children[i]); const ret = getContextMenuChild(children[i]);
if (ret) return ret.props.children; if (ret) return ret.props.children;
} }
} else if (isValid(children)) return children.props.children; } else if (isValid(children)) return children.props.children;
}; };
function patchAllContextMenus() {
const handleContextMenu = (_this, ret, noRender) => { const handleContextMenu = (_this, ret, noRender) => {
const menuGroups = getContextMenuChild(ret) || ret; const menuGroups = getContextMenuChild(ret) || ret;
if (!menuGroups) return Logger.warn('Failed to get context menu groups!', _this, ret); if (!menuGroups) return /* Logger.warn('Failed to get context menu groups!', _this, ret) */;
let [value, set] = noRender ? React.useState(false) : []; /* emulate a react class component */
let [state, setState] = noRender ? React.useState({}) : [];
/* emulate a react component */
if (noRender) { if (noRender) {
let [value, set] = React.useState(false);
let [state, setState] = React.useState({});
_this.forceUpdate = () => set(!value); _this.forceUpdate = () => set(!value);
_this.state = state; _this.state = state;
_this.setState = setState; _this.setState = setState;
@ -532,11 +571,36 @@ var XenoLib = (() => {
} }
}); });
}; };
existingContextMenus.forEach(type => { 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;
}
};
const renderContextMenus = ['NativeContextMenu', 'GuildRoleContextMenu', 'MessageContextMenu', 'DeveloperContextMenu', 'ScreenshareContextMenu'];
const hookContextMenus = [getModule(/case \w.ContextMenuTypes.CHANNEL_LIST_TEXT/), getModule(/case \w.ContextMenuTypes.GUILD_CHANNEL_LIST/), getModule(/case \w.ContextMenuTypes.USER_CHANNEL_MEMBERS/)];
for (const type of renderContextMenus) {
const module = WebpackModules.getByDisplayName(type); const module = WebpackModules.getByDisplayName(type);
if (!module) return Logger.warn(`Failed to find ContextMenu type`, type); if (!module) return Logger.warn(`Failed to find ContextMenu type`, type);
Patcher.after(module.prototype, 'render', (_this, _, ret) => handleContextMenu(_this, ret)); Patcher.after(module.prototype, 'render', (_this, _, ret) => handleContextMenu(_this, ret));
}); }
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));
}
}
const GroupDMContextMenu = WebpackModules.getByDisplayName('FluxContainer(GroupDMContextMenu)'); const GroupDMContextMenu = WebpackModules.getByDisplayName('FluxContainer(GroupDMContextMenu)');
if (GroupDMContextMenu) { if (GroupDMContextMenu) {
try { try {
@ -546,30 +610,13 @@ var XenoLib = (() => {
Logger.stacktrace('Failed patching GroupDMContextMenu', e); Logger.stacktrace('Failed patching GroupDMContextMenu', e);
} }
} }
function getModule(regex) { } catch (e) {
const modules = WebpackModules.getAllModules(); Logger.stacktrace('Failed to patch context menus', e);
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.originalsource.toString().search(regex) !== -1)) return module;
}
}
const somemoremenus = [getModule(/case \w.ContextMenuTypes.CHANNEL_LIST_TEXT/), getModule(/case \w.ContextMenuTypes.GUILD_CHANNEL_LIST/), getModule(/case \w.ContextMenuTypes.USER_CHANNEL_MEMBERS/)];
somemoremenus.forEach(menu => {
if (!menu) return Logger.warn('Special context menu is undefined!');
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));
}
});
XenoLib.__contextPatches.__contextPatched = true;
} }
XenoLib.patchContext = callback => { XenoLib.patchContext = callback => {
XenoLib.__contextPatches.push(callback); XenoLib.__contextPatches.push(callback);
}; };
class ContextMenuWrapper extends React.PureComponent { class ContextMenuWrapper extends React.PureComponent {
render() { render() {
return React.createElement('div', { className: DiscordClasses.ContextMenu.contextMenu }, this.props.menu); return React.createElement('div', { className: DiscordClasses.ContextMenu.contextMenu }, this.props.menu);
@ -581,269 +628,266 @@ var XenoLib = (() => {
} else { } else {
props.__XenoLib_ContextMenus = [menuCreation]; props.__XenoLib_ContextMenus = [menuCreation];
const oOnContextMenu = props.onContextMenu; const oOnContextMenu = props.onContextMenu;
props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, e => React.createElement(ContextMenuWrapper, { menu: props.__XenoLib_ContextMenus.map(m => m()), type }))); props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, _ => React.createElement(ContextMenuWrapper, { menu: props.__XenoLib_ContextMenus.map(m => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'shared context menu' }, m())), type })));
} }
}; };
if (global.XenoLib) if (global.XenoLib.__contextPatches && global.XenoLib.__contextPatches.length) XenoLib.__contextPatches.push(...global.XenoLib.__contextPatches); const ContextMenuSubMenuItem = WebpackModules.getByDisplayName('FluxContainer(SubMenuItem)');
XenoLib.unpatchContext = callback => XenoLib.__contextPatches.splice(XenoLib.__contextPatches.indexOf(callback), 1); XenoLib.unpatchContext = callback => XenoLib.__contextPatches.splice(XenoLib.__contextPatches.indexOf(callback), 1);
patchAllContextMenus(); /* prevent BDFDB from being a gay piece of crap by patching it first */ XenoLib.createContextMenuItem = (label, action, options = {}) => React.createElement(ContextMenuItem, { label, action: () => (!options.noClose && ContextMenuActions.closeContextMenu(), action()), ...options });
XenoLib.createContextMenuItem = (label, action, options = {}) => XenoLib.createContextMenuSubMenu = (label, items, options = {}) => React.createElement(ContextMenuSubMenuItem, { label, render: items, ...options });
React.createElement(ContextMenuItem, {
label,
action: () => {
if (!options.noClose) ContextMenuActions.closeContextMenu();
action();
},
...options
});
XenoLib.createContextMenuSubMenu = (label, items, options = {}) =>
React.createElement(ContextMenuSubMenuItem, {
label,
render: items,
...options
});
XenoLib.createContextMenuGroup = (children, options) => React.createElement(ContextMenuItemsGroup, { children, ...options }); XenoLib.createContextMenuGroup = (children, options) => React.createElement(ContextMenuItemsGroup, { children, ...options });
XenoLib._ = XenoLib.DiscordUtils = WebpackModules.getByProps('bindAll', 'debounce'); XenoLib._ = XenoLib.DiscordUtils = WebpackModules.getByProps('bindAll', 'debounce');
const dialog = require('electron').remote.dialog; try {
const showSaveDialog = dialog.showSaveDialogSync || dialog.showSaveDialog; XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('ButtonLink');
const showOpenDialog = dialog.showOpenDialogSync || dialog.showOpenDialog; XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions.default;
XenoLib.ReactComponents = {}; } catch (e) {
Logger.stacktrace('Error getting Button component', e);
XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('ButtonLink'); }
XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions.default;
/* shared between FilePicker and ColorPicker */
const MultiInputClassname = XenoLib.joinClassNames(DiscordClasses.BasicInputs.input.value, XenoLib.getClass('multiInput')); const MultiInputClassname = XenoLib.joinClassNames(DiscordClasses.BasicInputs.input.value, XenoLib.getClass('multiInput'));
const MultiInputFirstClassname = XenoLib.getClass('multiInputFirst'); const MultiInputFirstClassname = XenoLib.getClass('multiInputFirst');
const MultiInputFieldClassname = XenoLib.getClass('multiInputField'); const MultiInputFieldClassname = XenoLib.getClass('multiInputField');
const ErrorMessageClassname = XenoLib.getClass('input errorMessage'); const ErrorMessageClassname = XenoLib.getClass('input errorMessage');
const ErrorClassname = XenoLib.getClass('input error'); const ErrorClassname = XenoLib.getClass('input error');
const DelayedCall = WebpackModules.getByProps('DelayedCall').DelayedCall;
const FsModule = require('fs'); try {
/** const dialog = require('electron').remote.dialog;
* @interface const showOpenDialog = dialog.showOpenDialogSync || dialog.showOpenDialog;
* @name module:FilePicker const DelayedCall = WebpackModules.getByProps('DelayedCall').DelayedCall;
* @property {string} path const FsModule = require('fs');
* @property {string} placeholder /**
* @property {Function} onChange * @interface
* @property {object} properties * @name module:FilePicker
* @property {bool} nullOnInvalid * @property {string} path
*/ * @property {string} placeholder
XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent { * @property {Function} onChange
constructor(props) { * @property {object} properties
super(props); * @property {bool} nullOnInvalid
this.state = { */
multiInputFocused: false, XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent {
path: props.path, constructor(props) {
error: null super(props);
}; this.state = {
XenoLib._.bindAll(this, ['handleOnBrowse', 'handleChange']); multiInputFocused: false,
this.delayedCallVerifyPath = new DelayedCall(500, () => path: props.path,
FsModule.access(this.state.path, FsModule.constants.W_OK, error => { error: null
const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null; };
this.setState({ error: invalid }); XenoLib._.bindAll(this, ['handleOnBrowse', 'handleChange']);
if (invalid && this.props.nullOnInvalid) this.props.onChange(null); this.delayedCallVerifyPath = new DelayedCall(500, () =>
}) FsModule.access(this.state.path, FsModule.constants.W_OK, error => {
); const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null;
} this.setState({ error: invalid });
handleOnBrowse() { if (invalid && this.props.nullOnInvalid) this.props.onChange(null);
const path = showOpenDialog({ title: this.props.title, properties: this.props.properties }); })
if (Array.isArray(path) && path.length) this.handleChange(path[0]); );
} }
handleChange(path) { handleOnBrowse() {
this.props.onChange(path); const path = showOpenDialog({ title: this.props.title, properties: this.props.properties });
this.setState({ path }); if (Array.isArray(path) && path.length) this.handleChange(path[0]);
this.delayedCallVerifyPath.delay(); }
} handleChange(path) {
render() { this.props.onChange(path);
const n = {}; this.setState({ path });
n[DiscordClasses.BasicInputs.focused] = this.state.multiInputFocused; this.delayedCallVerifyPath.delay();
n[ErrorClassname] = !!this.state.error; }
return React.createElement( render() {
'div', const n = {};
{ className: DiscordClasses.BasicInputs.inputWrapper, style: { width: '100%' } }, n[DiscordClasses.BasicInputs.focused] = this.state.multiInputFocused;
React.createElement( n[ErrorClassname] = !!this.state.error;
return React.createElement(
'div', 'div',
{ className: XenoLib.joinClassNames(MultiInputClassname, n) }, { className: DiscordClasses.BasicInputs.inputWrapper, style: { width: '100%' } },
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 }),
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)
);
}
};
/**
* @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');
const Icon = WebpackModules.getByDisplayName('Icon');
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 },
React.createElement( React.createElement(
FormItem, 'div',
{ className: DiscordClasses.Margins.marginTop20 }, { className: XenoLib.joinClassNames(MultiInputClassname, n) },
React.createElement(WebpackModules.getByDisplayName('ColorPicker'), { React.createElement(DiscordModules.Textbox, {
defaultColor: this.props.defaultColor, value: this.state.path,
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], placeholder: this.props.placeholder,
value: this.state.value, onChange: this.handleChange,
onChange: this.handleChange onFocus: () => this.setState({ multiInputFocused: true }),
}) onBlur: () => this.setState({ multiInputFocused: false }),
) 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);
} }
class ColorPicker extends React.PureComponent { try {
constructor(props) { /**
super(props); * @param {string} name - name label of the setting
this.state = { * @param {string} note - help/note to show underneath or above the setting
error: null, * @param {string} value - current hex color
value: props.value, * @param {callable} onChange - callback to perform on setting change, callback receives hex string
multiInputFocused: false * @param {object} [options] - object of options to give to the setting
}; * @param {boolean} [options.disabled=false] - should the setting be disabled
XenoLib._.bindAll(this, ['handleChange', 'handleColorPicker', 'handleReset']); * @param {Array<number>} [options.colors=presetColors] - preset list of colors
} * @author Zerebos, from his library ZLibrary
handleChange(value) { */
if (!value.length) { const FormItem = WebpackModules.getByDisplayName('FormItem');
this.state.error = 'You must input a hex string'; const DeprecatedModal = WebpackModules.getByDisplayName('DeprecatedModal');
} else if (!ColorConverter.isValidHex(value)) { const ModalContainerClassname = XenoLib.getClass('mobile container');
this.state.error = 'Invalid hex string'; const ModalContentClassname = XenoLib.getClass('mobile container content');
} else {
this.state.error = null; const Icon = WebpackModules.getByDisplayName('Icon');
class ColorPickerModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = { value: props.value };
XenoLib._.bindAll(this, ['handleChange']);
} }
this.setState({ value }); handleChange(value) {
this.props.onChange(!value.length || !ColorConverter.isValidHex(value) ? this.props.defaultColor : value); this.setState({ value });
} this.props.onChange(ColorConverter.int2hex(value));
handleColorPicker() { }
ModalStack.push(e => React.createElement(ColorPickerModal, { ...e, defaultColor: ColorConverter.hex2int(this.props.defaultColor), value: ColorConverter.hex2int(this.props.value), onChange: this.handleChange })); render() {
} return React.createElement(
handleReset() { DeprecatedModal,
this.handleChange(this.props.defaultColor); { className: ModalContainerClassname, tag: 'form', onSubmit: this.handleSubmit, size: '' },
}
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(
'div',
{ 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
}),
React.createElement( React.createElement(
XenoLib.ReactComponents.Button, DeprecatedModal.Content,
{ { className: ModalContentClassname },
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: 'xenoLib-button button-34kXw5 button-3tQuzi'
},
React.createElement('span', { className: 'text-2sI5Sd' }, 'Color picker'),
React.createElement( React.createElement(
'span', FormItem,
{ { className: DiscordClasses.Margins.marginTop20 },
className: 'xenoLib-button-icon' React.createElement(WebpackModules.getByDisplayName('ColorPicker'), {
}, defaultColor: this.props.defaultColor,
React.createElement(Icon, { 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],
name: 'Dropper' value: this.state.value,
onChange: this.handleChange
}) })
) )
)
);
}
}
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;
}
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(
'div',
{ 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
}),
React.createElement(
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: 'xenoLib-button button-34kXw5 button-3tQuzi'
},
React.createElement('span', { className: 'text-2sI5Sd' }, 'Color picker'),
React.createElement(
'span',
{
className: 'xenoLib-button-icon'
},
React.createElement(Icon, {
name: 'Dropper'
})
)
),
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: 'xenoLib-button button-34kXw5 button-3tQuzi'
},
React.createElement('span', { className: 'text-2sI5Sd' }, 'Reset'),
React.createElement(
'span',
{
className: 'xenoLib-button-icon xenoLib-revert'
},
React.createElement(Icon, {
name: 'ClockReverse'
})
)
)
), ),
React.createElement( !!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error)
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: 'xenoLib-button button-34kXw5 button-3tQuzi'
},
React.createElement('span', { className: 'text-2sI5Sd' }, 'Reset'),
React.createElement(
'span',
{
className: 'xenoLib-button-icon xenoLib-revert'
},
React.createElement(Icon, {
name: 'ClockReverse'
})
)
)
),
!!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error)
);
} }
XenoLib.Settings = {};
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
});
}
};
} catch (e) {
Logger.stacktrace('Failed to create ColorPicker settings component', e);
} }
XenoLib.Settings = {};
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
});
}
};
XenoLib.changeName = (currentName, newName) => { XenoLib.changeName = (currentName, newName) => {
const path = require('path'); const path = require('path');
@ -1141,7 +1185,7 @@ var XenoLib = (() => {
this.setState({ hovered: true }); this.setState({ hovered: true });
}, },
onMouseLeave: e => { onMouseLeave: e => {
if (this.state.leaving || !this.props.timeout || this.state.closeFast) return; if (!this.state.hovered && (this.state.leaving || !this.props.timeout || this.state.closeFast)) return;
this._animationCancel(); this._animationCancel();
this.setState({ hovered: false }); this.setState({ hovered: false });
}, },
@ -1195,7 +1239,6 @@ var XenoLib = (() => {
}), }),
this.state.counter > 1 && BadgesModule.NumberBadge({ count: this.state.counter, className: 'xenLib-notification-counter', color: '#2196f3' }), this.state.counter > 1 && BadgesModule.NumberBadge({ count: this.state.counter, className: 'xenLib-notification-counter', color: '#2196f3' }),
this.state.contentParsed this.state.contentParsed
/* React.createElement('a', { onClick: this.closeNow }, 'close') */
) )
) )
); );
@ -1243,7 +1286,7 @@ var XenoLib = (() => {
XenoLib.changeName(__filename, '1XenoLib'); /* prevent user from changing libs filename */ XenoLib.changeName(__filename, '1XenoLib'); /* prevent user from changing libs filename */
const notifLocations = ['topLeft', 'topMiddle', 'topRight', 'bottomLeft', 'bottomMiddle', 'bottomRight']; const notifLocations = ['topLeft', 'topMiddle', 'topRight', 'bottomLeft', 'bottomMiddle', 'bottomRight'];
const notifLocationClasses = ['topLeft-3buHIc option-n0icdO', 'topMiddle-xenoLib option-n0icdO', 'topRight-3GKDeL option-n0icdO', 'bottomLeft-39-xss option-n0icdO', 'bottomMiddle-xenoLib option-n0icdO', 'bottomRight-1T56wW option-n0icdO']; 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')}`];
const PositionSelectorWrapperClassname = XenoLib.getClass('topLeft wrapper'); const PositionSelectorWrapperClassname = XenoLib.getClass('topLeft wrapper');
const PositionSelectorSelectedClassname = XenoLib.getClass('topLeft selected'); const PositionSelectorSelectedClassname = XenoLib.getClass('topLeft selected');
const PositionSelectorHiddenInputClassname = XenoLib.getClass('topLeft hiddenInput'); const PositionSelectorHiddenInputClassname = XenoLib.getClass('topLeft hiddenInput');
@ -1415,49 +1458,85 @@ var XenoLib = (() => {
const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() === 'confirm-modal'); const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() === 'confirm-modal');
const onFail = () => BdApi.getCore().alert(header, `${content}<br/>Due to a slight mishap however, you'll have to download the library yourself. After opening the link, do CTRL + S to download the library.<br/><br/><a href="https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js"target="_blank">Click here to download ZeresPluginLibrary</a>`); const onFail = () => BdApi.getCore().alert(header, `${content}<br/>Due to a slight mishap however, you'll have to download the library yourself. After opening the link, do CTRL + S to download the library.<br/><br/><a href="https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js"target="_blank">Click here to download ZeresPluginLibrary</a>`);
if (!ModalStack || !ConfirmationModal || !TextElement) return onFail(); if (!ModalStack || !ConfirmationModal || !TextElement) return onFail();
ModalStack.push(props => { let modalId;
const onHeckWouldYouLookAtThat = (() => {
if (!global.pluginModule || !global.BDEvents) return;
const onLoaded = e => {
if (e !== 'ZeresPluginLibrary') return;
BDEvents.off('plugin-loaded', onLoaded);
ModalStack.popWithKey(modalId); /* make it easier on the user */
pluginModule.reloadPlugin(this.name);
};
BDEvents.on('plugin-loaded', onLoaded);
return () => BDEvents.off('plugin-loaded', onLoaded);
})();
class TempErrorBoundary extends BdApi.React.PureComponent {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(err, inf) {
console.error(`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;
}
}
modalId = ModalStack.push(props => {
return BdApi.React.createElement( return BdApi.React.createElement(
ConfirmationModal, TempErrorBoundary,
Object.assign( {
{ label: 'missing dependency modal',
header, onError: () => {
children: [ ModalStack.popWithKey(modalId); /* smh... */
BdApi.React.createElement(TextElement, { onFail();
color: TextElement.Colors.PRIMARY, }
children: [`${content} Please click Download Now to install it.`] },
}) BdApi.React.createElement(
], ConfirmationModal,
red: false, Object.assign(
confirmText: 'Download Now', {
cancelText: 'Cancel', header,
onConfirm: () => { children: [
const request = require('request'); BdApi.React.createElement(TextElement, {
const fs = require('fs'); color: TextElement.Colors.PRIMARY,
const path = require('path'); children: [`${content} Please click Download Now to install it.`]
const onDone = () => { })
if (!global.pluginModule || !global.BDEvents) return; ],
const onLoaded = e => { red: false,
if (e !== 'ZeresPluginLibrary') return; confirmText: 'Download Now',
BDEvents.off('plugin-loaded', onLoaded); cancelText: 'Cancel',
pluginModule.reloadPlugin(this.name); onConfirm: () => {
onHeckWouldYouLookAtThat();
const request = require('request');
const fs = require('fs');
const path = require('path');
const onDone = () => {
if (!global.pluginModule || !global.BDEvents) return;
const onLoaded = e => {
if (e !== 'ZeresPluginLibrary') return;
BDEvents.off('plugin-loaded', onLoaded);
pluginModule.reloadPlugin(this.name);
};
BDEvents.on('plugin-loaded', onLoaded);
}; };
BDEvents.on('plugin-loaded', onLoaded); request('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (error, response, body) => {
}; if (error) return onFail();
request('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (error, response, body) => { onDone();
if (error) return onFail(); fs.writeFile(path.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), body, () => {});
onDone(); });
fs.writeFile(path.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), body, () => {}); }
}); },
} props
}, )
props
) )
); );
}); });
} }
start() {} start() {}
get name() { get name() {
return config.info.name; return config.info.name;
} }