PermissionViewer: improve role context menu

This commit is contained in:
Vendicated 2026-05-04 18:50:47 +02:00
parent b08fafb00b
commit 6a325e2c89
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
2 changed files with 112 additions and 81 deletions

View file

@ -10,9 +10,10 @@ import { ImageIcon } from "@components/Icons";
import { copyToClipboard } from "@utils/clipboard";
import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild, openImageModal } from "@utils/discord";
import { isTruthy } from "@utils/guards";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { PopoutProps } from "@vencord/discord-types";
import { Guild, PopoutProps, Role } from "@vencord/discord-types";
import { findByCodeLazy, findByPropsLazy, findCssClassesLazy } from "@webpack";
import { GuildRoleStore, Menu, PermissionStore, Popout, useRef } from "@webpack/common";
import { ComponentType } from "react";
@ -85,6 +86,90 @@ type RoleMemberPopout = ComponentType<RoleMemberPopoutProps>;
let RoleMemberPopout: RoleMemberPopout = () => null;
export function buildExtraRoleContextMenuItems(role: Role, guild: Guild, popoutRef?: React.RefObject<any>) {
if (!role) return { before: [], after: [] };
const before = [
PermissionStore.getGuildPermissionProps(guild).canManageRoles && (
<Menu.MenuItem
key="vc-edit-role"
id="vc-edit-role"
label="Edit Role"
action={async () => {
await GuildSettingsActions.open(guild.id, "ROLES");
GuildSettingsActions.selectRole(role.id);
}}
icon={PencilIcon}
/>
),
role.colorString && (
<Menu.MenuItem
key="vc-copy-role-color"
id="vc-copy-role-color"
label="Copy Role Color"
action={() => copyToClipboard(role.colorString!)}
icon={AppearanceIcon}
/>
)
].filter(isTruthy);
const after = [
role.icon && (
<Menu.MenuItem
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal({
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
height: 128,
width: 128
});
}}
icon={ImageIcon}
/>
),
popoutRef && (
<Menu.MenuItem
key="vc-view-role-members"
id="vc-view-role-members"
label="View Role Members"
render={() => (
<Popout
position="right"
align="center"
targetElementRef={popoutRef}
preload={() => loadRoleMembers(guild.id, role.id)}
renderPopout={popoutProps => (
<RoleMemberPopout
popoutProps={popoutProps}
guildId={guild.id}
channelId={getCurrentChannel()!.id}
roleId={role.id}
/>
)}
>
{popoutProps => (
<div
className={classes(MenuItemClasses.item, MenuItemClasses.labelContainer, MenuItemClasses.colorDefault)}
ref={popoutRef}
role="menuitem"
{...popoutProps}
>
<div className={MenuItemClasses.label}>View Role Members</div>
<div className={MenuItemClasses.iconContainer}>
<RoleMembersIcon />
</div>
</div>
)}
</Popout>
)}
/>
)
].filter(isTruthy);
return { before, after };
}
export default definePlugin({
name: "BetterRoleContext",
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
@ -121,81 +206,9 @@ export default definePlugin({
const role = GuildRoleStore.getRole(guild.id, id);
if (!role) return;
if (role.colorString) {
children.unshift(
<Menu.MenuItem
id="vc-copy-role-color"
label="Copy Role Color"
action={() => copyToClipboard(role.colorString!)}
icon={AppearanceIcon}
/>
);
}
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.unshift(
<Menu.MenuItem
id="vc-edit-role"
label="Edit Role"
action={async () => {
await GuildSettingsActions.open(guild.id, "ROLES");
GuildSettingsActions.selectRole(id);
}}
icon={PencilIcon}
/>
);
}
if (role.icon) {
children.push(
<Menu.MenuItem
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal({
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
height: 128,
width: 128
});
}}
icon={ImageIcon}
/>
);
}
children.push(
<Menu.MenuItem id="vc-view-role-members" label="View Role Members" render={() => (
<Popout
position="right"
align="center"
targetElementRef={popoutRef}
preload={() => loadRoleMembers(guild.id, id)}
renderPopout={popoutProps => (
<RoleMemberPopout
popoutProps={popoutProps}
guildId={guild.id}
channelId={getCurrentChannel()!.id}
roleId={id}
/>
)}
>
{popoutProps => (
<div
className={classes(MenuItemClasses.item, MenuItemClasses.labelContainer, MenuItemClasses.colorDefault)}
ref={popoutRef}
role="menuitem"
{...popoutProps}
>
<div className={MenuItemClasses.label}>View Role Members</div>
<div className={MenuItemClasses.iconContainer}>
<RoleMembersIcon />
</div>
</div>
)}
</Popout>
)} />
);
const { before, after } = buildExtraRoleContextMenuItems(role, guild, popoutRef);
children.unshift(...before);
children.push(...after);
}
}
});

View file

@ -18,7 +18,8 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
import { InfoIcon, OwnerCrownIcon, RightArrow } from "@components/Icons";
import { buildExtraRoleContextMenuItems } from "@plugins/betterRoleContext";
import { cl, getGuildPermissionSpecMap, loadGetGuildPermissionSpecMap } from "@plugins/permissionsViewer/utils";
import { copyToClipboard } from "@utils/clipboard";
import { getIntlMessage, getUniqueUsername } from "@utils/discord";
@ -26,7 +27,7 @@ import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, Mod
import { Guild, Role, RoleOrUserPermission, UnicodeEmoji, User } from "@vencord/discord-types";
import { PermissionOverwriteType } from "@vencord/discord-types/enums";
import { findByCodeLazy } from "@webpack";
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common";
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, useRef, UserStore, useState, useStateFromStores } from "@webpack/common";
import { settings } from "..";
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
@ -204,25 +205,42 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
);
}
function IDIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24">
<path fill="currentColor" d="M15.3 14.48c-.46.45-1.08.67-1.86.67h-1.39V9.2h1.39c.78 0 1.4.22 1.86.67.46.45.68 1.22.68 2.31 0 1.1-.22 1.86-.68 2.31Z" />
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd" d="M5 2a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm1 15h2.04V7.34H6V17Zm4-9.66V17h3.44c1.46 0 2.6-.42 3.38-1.25.8-.83 1.2-2.02 1.2-3.58s-.4-2.75-1.2-3.58c-.79-.83-1.92-1.25-3.38-1.25H10Z" />
</svg>
);
}
function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: string; onClose: () => void; }) {
const popoutRef = useRef(null);
const role = GuildRoleStore.getRole(guild.id, roleId);
const { before, after } = buildExtraRoleContextMenuItems(role, guild, popoutRef);
return (
<Menu.Menu
navId={cl("role-context-menu")}
onClose={ContextMenuApi.closeContextMenu}
aria-label="Role Options"
>
{before}
<Menu.MenuItem
id={cl("copy-role-id")}
label={getIntlMessage("COPY_ID_ROLE")}
action={() => {
copyToClipboard(roleId);
}}
icon={IDIcon}
action={() => copyToClipboard(roleId)}
/>
{after}
{(settings.store as any).unsafeViewAsRole && (
<Menu.MenuItem
id={cl("view-as-role")}
label={getIntlMessage("VIEW_AS_ROLE")}
icon={() => <RightArrow height={18} width={18} />}
action={() => {
const role = GuildRoleStore.getRole(guild.id, roleId);
if (!role) return;