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.
vizality/renderer/src/builtins/settings/index.js

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}')` };
}
});
}
}