improve and modularize SettingsContextMenu component

- Moved the ContextMenu out of the settings builtin and renamed it to `SettingsContextMenu`. It can now be accessed with `@vizality/components/vizality`
- Changed the format of the addon items
- Added quick toggle feature to addon submenus, which can be activated by clicking the button or holding shift
pull/67/head
dperolio 3 years ago
parent 7d09c1a03c
commit 47660b84c4
No known key found for this signature in database
GPG Key ID: 3E9BBAA710D3DDCE

@ -1,148 +0,0 @@
import React, { memo } from 'react';
import { ContextMenu, LazyImage } from '@vizality/components';
import { toPlural } from '@vizality/util/string';
import { useForceUpdate } from '@vizality/hooks';
import { Messages } from '@vizality/i18n';
export default memo(() => {
const forceUpdate = useForceUpdate();
const plugins =
vizality.manager.plugins.keys
.sort((a, b) => a - b)
.map(plugin => vizality.manager.plugins.get(plugin));
const themes =
vizality.manager.themes.keys
.sort((a, b) => a - b)
.map(theme => vizality.manager.themes.get(theme));
const renderContextItem = (item, type) => {
return (
<ContextMenu.CheckboxItem
vz-addon-icon={item.manifest.icon}
id={item.addonId}
label={item.manifest.name}
checked={vizality.manager[toPlural(type)].isEnabled(item.addonId)}
action={async () => {
vizality.manager[toPlural(type)].isEnabled(item.addonId)
? await vizality.manager[toPlural(type)].disable(item.addonId)
: await vizality.manager[toPlural(type)].enable(item.addonId);
forceUpdate();
}}
>
<LazyImage src={item.manifest.icon} />
</ContextMenu.CheckboxItem>
);
};
return (
<>
<ContextMenu.Separator />
<ContextMenu.Item
id='vizality'
label='Vizality'
action={() => vizality.api.routes.navigateTo('dashboard')}
>
<ContextMenu.Item
id='settings'
label='Settings'
action={() => vizality.api.routes.navigateTo('settings')}
/>
<ContextMenu.Item
id='plugins'
label='Plugins'
action={() => vizality.api.routes.navigateTo('plugins')}
>
{plugins.length && plugins.map(plugin => renderContextItem(plugin, 'plugins'))}
</ContextMenu.Item>
<ContextMenu.Item
id='themes'
label='Themes'
action={() => vizality.api.routes.navigateTo('themes')}
>
{themes.length && themes.map(theme => renderContextItem(theme, 'themes'))}
</ContextMenu.Item>
<ContextMenu.Item
id='snippets'
label='Snippets'
disabled={true}
action={() => vizality.api.routes.navigateTo('snippets')}
>
</ContextMenu.Item>
{vizality.manager.builtins.isEnabled('quick-code') && (
<ContextMenu.Item
id='quick-code'
label='Quick Code'
action={() => vizality.api.routes.navigateTo('quick-code')}
/>
)}
<ContextMenu.Separator/>
<ContextMenu.Item
id='development'
label='Development'
action={() => vizality.api.routes.navigateTo('development')}
>
<ContextMenu.Item
id='documentation'
label='Documentation'
action={() => vizality.api.routes.navigateTo('docs')}
>
<ContextMenu.Item
id='getting-started'
label='Getting Started'
action={() => vizality.api.routes.navigateTo('docs/getting-started')}
/>
<ContextMenu.Item
id='plugins'
label='Plugins'
action={() => vizality.api.routes.navigateTo('docs/plugins')}
/>
<ContextMenu.Item
id='themes'
label='Themes'
action={() => vizality.api.routes.navigateTo('docs/themes')}
/>
<ContextMenu.Item
id='screenshots'
label='Screenshots'
action={() => vizality.api.routes.navigateTo('docs/components/screenshots')}
/>
<ContextMenu.Item
id='icons'
label='Components'
action={() => vizality.api.routes.navigateTo('docs/components/icons')}
/>
<ContextMenu.Item
id='markdown'
label='Markdown'
action={() => vizality.api.routes.navigateTo('docs/components/markdown')}
/>
<ContextMenu.Item
id='error-test'
label='Error Test'
action={() => vizality.api.routes.navigateTo('docs/components/error-test')}
/>
<ContextMenu.Item
id='test'
label='Test'
action={() => vizality.api.routes.navigateTo('docs/components/test')}
/>
</ContextMenu.Item>
</ContextMenu.Item>
<ContextMenu.Separator/>
<ContextMenu.Item
id='updater'
label='Updater'
action={() => vizality.api.routes.navigateTo('updater')}
/>
<ContextMenu.Item
id='changelog'
label='Changelog'
action={() => vizality.api.routes.navigateTo('changelog')}
/>
</ContextMenu.Item>
</>
);
});

@ -2,13 +2,13 @@ import React from 'react';
import { open as openModal, close as closeModal } from '@vizality/modal';
import { getModuleByDisplayName, getModule } from '@vizality/webpack';
import { SettingsContextMenu } from '@vizality/components/vizality';
import { joinClassNames } from '@vizality/util/dom';
import { patch, unpatch } from '@vizality/patcher';
import { Confirm } from '@vizality/components';
import { Builtin } from '@vizality/entities';
import { Messages } from '@vizality/i18n';
import ContextMenu from './components/ContextMenu';
import _Settings from './components/Settings';
export default class Settings extends Builtin {
@ -99,10 +99,21 @@ export default class Settings extends Builtin {
}
patchSettingsContextMenu () {
const SettingsContextMenu = getModule(m => m.default?.displayName === 'UserSettingsCogContextMenu');
patch('vz-settings-context-menu', SettingsContextMenu, 'default', (_, res) => {
const DiscordSettingsContextMenu = getModule(m => m.default?.displayName === 'UserSettingsCogContextMenu');
patch('vz-settings-context-menu', DiscordSettingsContextMenu, 'default', (_, res) => {
const items = res.props.children.find(child => Array.isArray(child));
items.push(ContextMenu.type().props.children[1]);
items.push(
<>
<ContextMenu.Separator />
<ContextMenu.Item
id='vizality-dashboard'
label='Vizality'
action={() => vizality.api.routes.navigateTo('dashboard')}
>
{SettingsContextMenu.type().props.children}
</ContextMenu.Item>
</>
);
});
}

@ -88,6 +88,7 @@ export { default as Icon } from './Icon';
export * as dashboard from './dashboard';
export * as settings from './settings';
export * as vizality from './vizality';
export * as addon from './addon';
export * as misc from './misc';

@ -1,9 +1,5 @@
import { getModuleByDisplayName } from '@vizality/webpack';
import AsyncComponent from '../AsyncComponent';
export const SwitchItem = AsyncComponent.fromDisplayName('SwitchItem');
// export { default as PermissionOverrideItem } from './PermissionOverrideItem';
export { default as ColorPickerInput } from './ColorPickerInput';
export { default as RegionSelector } from './RegionSelector';
@ -11,6 +7,7 @@ export { default as SelectInput } from './SelectInput';
export { default as SliderInput } from './SliderInput';
export { default as ButtonItem } from './ButtonItem';
export { default as RadioGroup } from './RadioGroup';
export { default as SwitchItem } from './SwitchItem';
export { default as CopyInput } from './CopyInput';
export { default as FormTitle } from './FormTitle';
export { default as TextInput } from './TextInput';
@ -19,6 +16,10 @@ export { default as Checkbox } from './Checkbox';
export { default as TextArea } from './TextArea';
export { default as FormItem } from './FormItem';
// Re-export module properties
getModuleByDisplayName('SwitchItem', true, true).then(SwitchItem =>
[ 'Sizes', 'Themes' ].forEach(prop => this.SwitchItem[prop] = SwitchItem[prop]));
/**
* Re-export module properties.
*/
getModuleByDisplayName('SwitchItem', true, true).then(SwitchItem => {
this.SwitchItem.Sizes = SwitchItem.Sizes;
this.SwitchItem.Themes = SwitchItem.Themes;
});

@ -0,0 +1,333 @@
/* eslint-disable no-unused-vars */
import React, { memo, useState, useEffect } from 'react';
import { toPlural } from '@vizality/util/string';
import { useForceUpdate } from '@vizality/hooks';
import { contextMenu } from '@vizality/webpack';
import { error } from '@vizality/util/logger';
import { Messages } from '@vizality/i18n';
import { ContextMenu, LazyImage, Button, Tooltip } from '..';
import { AddonContextMenu } from '../addon';
const { closeContextMenu } = contextMenu;
/**
* @private
*/
const _labels = [ 'Component', 'Vizality', 'SettingsContextMenu' ];
const _error = (...message) => error({ labels: _labels, message });
/**
* Vizality dashboard settings context menu that is like a mini-dashboard. You're able
* to quickly access the Vizality dashboard and many of the pages that are
* accessible on the Vizality dashboard.
*
* By default, this component is implemented and can be accessed in various places around
* the app, such as the right click the gear on the user account panel which opens the
* Discord settings context menu. It can also be accessed by right clicking on the
* private channels Dashboard item.
* @component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export default memo(() => {
const [ quickToggle, setQuickToggle ] = useState(false);
const [ quickToggleKeybind, setQuickToggleKeybind ] = useState(false);
const forceUpdate = useForceUpdate();
/**
* Add some key event listeners on mount to allow users to hold shift to enter
* quick toggle mode on addon context items.
*/
useEffect(() => {
/**
* Handles keydown events.
* @param {document#event:keydown} evt Keydown event
*/
const keyDownHandler = evt => {
/**
* Check for the shift key.
*/
if (evt.keyCode === 16) {
setQuickToggle(true);
setQuickToggleKeybind(true);
}
};
/**
* Handles keyup events.
* @param {document#event:keyup} evt Keyup event
*/
const keyUpHandler = evt => {
/**
* Check for the shift key.
*/
if (evt.keyCode === 16) {
setQuickToggle(false);
setQuickToggleKeybind(false);
}
};
document.addEventListener('keydown', keyDownHandler);
document.addEventListener('keyup', keyUpHandler);
/**
* Remove the event listeners on dismount.
*/
return () => {
document.removeEventListener('keydown', keyDownHandler);
document.removeEventListener('keyup', keyUpHandler);
};
}, [ quickToggle ]);
/**
* Creates a sorted array containing all installed plugins.
*/
const plugins =
vizality.manager.plugins.keys
.sort((a, b) => a - b)
.map(plugin => vizality.manager.plugins.get(plugin));
/**
* Creates a sorted array containing all installed themes.
*/
const themes =
vizality.manager.themes.keys
.sort((a, b) => a - b)
.map(theme => vizality.manager.themes.get(theme));
/**
* Renders a context menu item for an addon.
* @param {Addon#addonId} addonId Addon ID
* @param {AddonManifest#name} name Addon name
* @param {AddonManifest#icon} icon Addon icon
* @param {AddonType} type Addon type
*/
const renderContextItem = (addonId, name, icon, type) => {
const _AddonContextMenu = AddonContextMenu.type({ addonId, type }).props.children;
return (
quickToggle
? (
<ContextMenu.CheckboxItem
vz-addon-icon={icon}
id={addonId}
label={name}
checked={vizality.manager[toPlural(type)].isEnabled(addonId)}
action={async () => {
try {
vizality.manager[toPlural(type)].isEnabled(addonId)
? await vizality.manager[toPlural(type)].disable(addonId)
: await vizality.manager[toPlural(type)].enable(addonId);
forceUpdate();
} catch (err) {
_error(err);
}
}}
>
<LazyImage src={icon} />
</ContextMenu.CheckboxItem>
)
: (
<ContextMenu.Item
vz-addon-icon={icon}
id={addonId}
label={name}
action={() => {
try {
if (vizality.manager[toPlural(type)].isCommunity(addonId)) {
return vizality.api.routes.navigateTo(`${toPlural(type)}/${addonId}`);
}
return vizality.api.routes.navigateTo(`${toPlural(type)}/local/${addonId}`);
} catch (err) {
_error(err);
}
}}
>
{_AddonContextMenu}
</ContextMenu.Item>
)
);
};
/**
* Renders an array of addon context items.
* @param {Array<Addon>} addons Addons
* @param {AddonType} type Addon type
* @returns {Array<React.Component>}
*/
const renderAddonItems = (addons, type) => {
return addons.map(addon => renderContextItem(addon.addonId, addon.manifest.name, addon.manifest.icon, type));
};
/**
* Renders a group of addon context items.
* @param {Array<Addon>} addons Addons
* @param {AddonType} type Addon type
* @returns {React.Component}
*/
const renderAddonGroup = (addons, type) => (
<ContextMenu.Group
id='quick-toggle'
className='vz-settings-context-menu-quick-toggle'
label={
<Tooltip text='Or you can just hold shift!'>
<Button
disabled={quickToggleKeybind}
onClick={() => setQuickToggle(!quickToggle)}
size={Button.Sizes.SMALL}
color={quickToggle && !quickToggleKeybind ? Button.Colors.RED : Button.Colors.BRAND}
>
{quickToggle && !quickToggleKeybind ? 'Disable' : 'Enable'} Quick Toggle Mode
</Button>
</Tooltip>
}
>
{renderAddonItems(addons, type)}
</ContextMenu.Group>
);
return (
<ContextMenu.Menu navId='vizality-dashboard' onClose={closeContextMenu}>
<ContextMenu.Item
id='settings'
label='Settings'
action={() => vizality.api.routes.navigateTo('settings')}
>
<ContextMenu.Item
id='general'
label='General'
action={() => vizality.api.routes.navigateTo('settings/general')}
/>
<ContextMenu.Item
id='account'
label='Account'
action={() => vizality.api.routes.navigateTo('settings/account')}
disabled
/>
<ContextMenu.Item
id='appearance'
label='Appearance'
action={() => vizality.api.routes.navigateTo('settings/appearance')}
disabled
/>
<ContextMenu.Item
id='notifications'
label='Notifications'
action={() => vizality.api.routes.navigateTo('settings/notifications')}
/>
<ContextMenu.Item
id='keybinds'
label='Keybinds'
action={() => vizality.api.routes.navigateTo('settings/keybinds')}
disabled
/>
<ContextMenu.Item
id='commands'
label='Commands'
action={() => vizality.api.routes.navigateTo('settings/commands')}
/>
<ContextMenu.Item
id='developer'
label='Developer'
action={() => vizality.api.routes.navigateTo('settings/developer')}
/>
<ContextMenu.Item
id='advanced'
label='Advanced'
action={() => vizality.api.routes.navigateTo('settings/advanced')}
/>
</ContextMenu.Item>
<ContextMenu.Item
id='plugins'
label='Plugins'
action={() => vizality.api.routes.navigateTo('plugins')}
>
{plugins.length && renderAddonGroup(plugins, 'plugin')}
</ContextMenu.Item>
<ContextMenu.Item
id='themes'
label='Themes'
action={() => vizality.api.routes.navigateTo('themes')}
>
{themes.length && renderAddonGroup(themes, 'theme')}
</ContextMenu.Item>
<ContextMenu.Item
id='snippets'
label='Snippets'
action={() => vizality.api.routes.navigateTo('snippets')}
disabled
>
</ContextMenu.Item>
{vizality.manager.builtins.isEnabled('quick-code') && (
<ContextMenu.Item
id='quick-code'
label='Quick Code'
action={() => vizality.api.routes.navigateTo('quick-code')}
/>
)}
<ContextMenu.Separator/>
<ContextMenu.Item
id='development'
label='Development'
action={() => vizality.api.routes.navigateTo('development')}
>
<ContextMenu.Item
id='documentation'
label='Documentation'
action={() => vizality.api.routes.navigateTo('docs')}
>
<ContextMenu.Item
id='getting-started'
label='Getting Started'
action={() => vizality.api.routes.navigateTo('docs/getting-started')}
/>
<ContextMenu.Item
id='plugins'
label='Plugins'
action={() => vizality.api.routes.navigateTo('docs/plugins')}
/>
<ContextMenu.Item
id='themes'
label='Themes'
action={() => vizality.api.routes.navigateTo('docs/themes')}
/>
<ContextMenu.Item
id='screenshots'
label='Screenshots'
action={() => vizality.api.routes.navigateTo('docs/components/screenshots')}
/>
<ContextMenu.Item
id='icons'
label='Components'
action={() => vizality.api.routes.navigateTo('docs/components/icons')}
/>
<ContextMenu.Item
id='markdown'
label='Markdown'
action={() => vizality.api.routes.navigateTo('docs/components/markdown')}
/>
<ContextMenu.Item
id='error-test'
label='Error Test'
action={() => vizality.api.routes.navigateTo('docs/components/error-test')}
/>
<ContextMenu.Item
id='test'
label='Test'
action={() => vizality.api.routes.navigateTo('docs/components/test')}
/>
</ContextMenu.Item>
</ContextMenu.Item>
<ContextMenu.Separator/>
<ContextMenu.Item
id='updater'
label='Updater'
action={() => vizality.api.routes.navigateTo('updater')}
/>
<ContextMenu.Item
id='changelog'
label='Changelog'
action={() => vizality.api.routes.navigateTo('changelog')}
/>
</ContextMenu.Menu>
);
});

@ -0,0 +1 @@
export { default as SettingsContextMenu } from './SettingsContextMenu';
Loading…
Cancel
Save