mirror of https://github.com/vizality/vizality
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
8.1 KiB
274 lines
8.1 KiB
import { openModal, closeModal } from '@vizality/modal';
|
|
import { getModule, contextMenu } from '@vizality/webpack';
|
|
import { SettingsContextMenu } from '@vizality/components/vizality';
|
|
import { Content, Layout } from '@vizality/components/dashboard';
|
|
import { toPlural } from '@vizality/util/string';
|
|
import { Confirm, ContextMenu, Modal } from '@vizality/components';
|
|
import { joinClassNames, waitForElement } from '@vizality/util/dom';
|
|
import { patch, unpatch } from '@vizality/patcher';
|
|
import { Builtin } from '@vizality/entities';
|
|
import { Messages } from '@vizality/i18n';
|
|
import React from 'react';
|
|
import { webFrame } from 'electron'
|
|
|
|
import SettingsPage from './components/Settings';
|
|
|
|
/**
|
|
* The settings page categories.
|
|
*/
|
|
const categories = [
|
|
'general',
|
|
'account',
|
|
'appearance',
|
|
'notifications',
|
|
'keybinds',
|
|
'commands',
|
|
'developer',
|
|
'advanced'
|
|
];
|
|
|
|
const content = {
|
|
header: 'Settings',
|
|
icon: 'gear'
|
|
};
|
|
|
|
export default class Settings extends Builtin {
|
|
start () {
|
|
this.pie = [];
|
|
this.injectStyles('styles/main.scss');
|
|
/**
|
|
* Register a route for each of the settings categories.
|
|
*/
|
|
categories.forEach(category =>
|
|
vizality.api.routes.registerRoute({
|
|
id: `settings/${category}`,
|
|
path: `/settings/${category}`,
|
|
render: props => (
|
|
<Layout>
|
|
<Content
|
|
header={content.header}
|
|
Separator={false}
|
|
icon={content.icon}
|
|
vz-builtin={this.addonId}
|
|
vz-section={category}
|
|
>
|
|
<SettingsPage
|
|
{...props}
|
|
builtin={this}
|
|
section={category}
|
|
/>
|
|
</Content>
|
|
</Layout>
|
|
)
|
|
})
|
|
);
|
|
|
|
vizality.api.settings._registerBuiltinSection({
|
|
header: 'Settings',
|
|
icon: content.icon,
|
|
render: props => <SettingsPage {...props} builtin={this} section='general' />
|
|
});
|
|
|
|
vizality.api.actions.registerAction('CONFIRM_RESTART', () => this.confirmRestart());
|
|
|
|
this.patchSettingsContextMenu();
|
|
this.patchSettingsContextMenuAddonItem();
|
|
this.patchSettingsContextMenuAddonCheckboxItem();
|
|
}
|
|
|
|
stop () {
|
|
categories.forEach(category => vizality.api.routes.unregisterRoute(`settings/${category}`));
|
|
vizality.api.routes.unregisterRoute('settings');
|
|
vizality.api.actions.unregisterAction('CONFIRM_RESTART');
|
|
unpatch('vz-settings-context-menu');
|
|
unpatch('vz-settings-context-menu-addon-items');
|
|
unpatch('vz-settings-context-menu-addon-checkbox-items');
|
|
}
|
|
|
|
confirmRestart () {
|
|
const { colorStandard } = getModule('colorStandard');
|
|
const { spacing } = getModule('spacing', 'message');
|
|
const { size16 } = getModule('size16');
|
|
|
|
openModal(() => props => (
|
|
<Modal.Root {...props}>
|
|
<Confirm
|
|
{...props}
|
|
red
|
|
header={Messages.ERRORS_RESTART_APP}
|
|
confirmText={Messages.BUNDLE_READY_RESTART}
|
|
cancelText={Messages.BUNDLE_READY_LATER}
|
|
onConfirm={() => DiscordNative.app.relaunch()}
|
|
onCancel={closeModal}
|
|
>
|
|
<div className={joinClassNames(colorStandard, spacing, size16)}>
|
|
{Messages.VIZALITY_SETTINGS_RESTART}
|
|
</div>
|
|
</Confirm>
|
|
</Modal.Root>
|
|
));
|
|
}
|
|
|
|
async waitFor(filter) {
|
|
const exists = await getModule(filter, true, true)
|
|
if (exists) return exists
|
|
return await this.waitFor(filter)
|
|
}
|
|
|
|
async patchSettingsContextMenu () {
|
|
const { panels } = await getModule('panels', 'downloadProgressCircle', 'hasNotice', true)
|
|
const { container } = await getModule('container', 'usernameContainer', 'godlike', true)
|
|
const { button } = await getModule('button', 'disabled', 'enabled', true)
|
|
|
|
await waitForElement(`.${panels} > .${container} .${button}:last-child`);
|
|
const settingsNode = webFrame.top.context.document.querySelector(`.${panels} > .${container} .${button}:last-child`);
|
|
|
|
settingsNode.__reactProps$.onContextMenu({
|
|
stopPropagation() {},
|
|
currentTarget: { contains: () => true },
|
|
preventDefault() {}
|
|
});
|
|
contextMenu.closeContextMenu();
|
|
|
|
|
|
const DiscordSettingsContextMenu = await this.waitFor(m => {
|
|
if (!m.default) return;
|
|
const string = String(m.default);
|
|
return string.includes('.AnalyticsLocationProvider,') && string.includes('.createElement(') && !string.includes('return function') && !m.
|
|
default.displayName;
|
|
});
|
|
|
|
patch('vz-settings-context-menu', DiscordSettingsContextMenu, 'default', (_, res) => {
|
|
const { children } = res.props;
|
|
const old = children.type;
|
|
|
|
children.type = () => {
|
|
const result = old(children.props);
|
|
|
|
const items = result.props.children.props.children.find(child => Array.isArray(child));
|
|
|
|
items.push(
|
|
<>
|
|
<ContextMenu.Separator />
|
|
<ContextMenu.Item
|
|
id='vizality-dashboard'
|
|
label='Vizality'
|
|
action={() => vizality.api.routes.navigateTo('dashboard')}
|
|
>
|
|
{SettingsContextMenu.type().props.children}
|
|
</ContextMenu.Item>
|
|
</>
|
|
);
|
|
|
|
return result;
|
|
};
|
|
});
|
|
}
|
|
|
|
patchSettingsContextMenuAddonCheckboxItem () {
|
|
const MenuCheckboxItem = getModule(m => m.default?.displayName === 'MenuCheckboxItem');
|
|
patch('vz-settings-context-menu-addon-checkbox-items', MenuCheckboxItem, 'default', ([ props ], res) => {
|
|
if (!res.props?.id) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* If the ID doesn't start with one of the items we're targetting, don't patch it.
|
|
*/
|
|
if ((res.props.id.indexOf('vizality-dashboard--plugins--') > 0 &&
|
|
res.props.id.indexOf('vizality-dashboard--themes--') > 0) ||
|
|
res.props['vz-addon-icon']
|
|
) return;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
const type =
|
|
new RegExp(/(vizality-dashboard-?-plugins)/).test(res.props.id)
|
|
? 'plugin'
|
|
: new RegExp(/(vizality-dashboard-?-themes)/).test(res.props.id)
|
|
? 'theme'
|
|
: null;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
if (type !== 'plugin' && type !== 'theme') {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
const addonId = res.props.id.replace(new RegExp(`(user-settings-cog-)?(vizality-dashboard-?-${toPlural(type)}--)`), '');
|
|
|
|
/**
|
|
*
|
|
*/
|
|
if (!vizality.manager[toPlural(type)].isInstalled(addonId)) {
|
|
return;
|
|
}
|
|
|
|
const addonIconUrl = props['vz-addon-icon'];
|
|
res.props['vz-addon-icon'] = '';
|
|
res.props['vz-addon-id'] = props.id;
|
|
if (addonIconUrl) {
|
|
res.props.style = { ...res.props.style, '--vz-addon-icon': `url('${addonIconUrl}')` };
|
|
}
|
|
});
|
|
}
|
|
|
|
patchSettingsContextMenuAddonItem () {
|
|
const MenuItem = getModule(m => m.default?.displayName === 'MenuItem');
|
|
patch('vz-settings-context-menu-addon-items', MenuItem, 'default', ([ props ], res) => {
|
|
if (!res.props?.id) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* If the ID doesn't start with one of the items we're targetting, don't patch it.
|
|
*/
|
|
if ((res.props.id.indexOf('vizality-dashboard--plugins--') > 0 &&
|
|
res.props.id.indexOf('vizality-dashboard--themes--') > 0) ||
|
|
res.props['vz-addon-icon']
|
|
) return;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
const type =
|
|
new RegExp(/(vizality-dashboard-?-plugins)/).test(res.props.id)
|
|
? 'plugin'
|
|
: new RegExp(/(vizality-dashboard-?-themes)/).test(res.props.id)
|
|
? 'theme'
|
|
: null;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
if (type !== 'plugin' && type !== 'theme') {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
const addonId = res.props.id.replace(new RegExp(`(user-settings-cog-)?(vizality-dashboard-?-${toPlural(type)}--)`), '');
|
|
|
|
/**
|
|
*
|
|
*/
|
|
if (!vizality.manager[toPlural(type)].isInstalled(addonId)) {
|
|
return;
|
|
}
|
|
|
|
const addonIconUrl = props['vz-addon-icon'];
|
|
res.props['vz-addon-icon'] = '';
|
|
res.props['vz-addon-id'] = props.id;
|
|
if (addonIconUrl) {
|
|
res.props.style = { ...res.props.style, '--vz-addon-icon': `url('${addonIconUrl}')` };
|
|
}
|
|
});
|
|
}
|
|
}
|