PermissionsViewer: Show RoleIcons & which role grants permission (#2824)

This commit is contained in:
vishnyanetchereshnya 2024-09-02 06:42:52 +03:00 committed by GitHub
parent 74fd85bd3d
commit 0c71d6c3fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 227 additions and 187 deletions

View file

@ -21,8 +21,10 @@ import { Flex } from "@components/Flex";
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
import { getUniqueUsername } from "@utils/discord";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
import type { Guild } from "discord-types/general";
import { findByCodeLazy } from "@webpack";
import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
import { UnicodeEmoji } from "@webpack/types";
import type { Guild, Role, User } from "discord-types/general";
import { settings } from "..";
import { cl, getPermissionDescription, getPermissionString } from "../utils";
@ -42,15 +44,15 @@ export interface RoleOrUserPermission {
overwriteDeny?: bigint;
}
function openRolesAndUsersPermissionsModal(permissions: Array<RoleOrUserPermission>, guild: Guild, header: string) {
return openModal(modalProps => (
<RolesAndUsersPermissions
modalProps={modalProps}
permissions={permissions}
guild={guild}
header={header}
/>
));
type GetRoleIconData = (role: Role, size: number) => { customIconSrc?: string; unicodeEmoji?: UnicodeEmoji; };
const getRoleIconData: GetRoleIconData = findByCodeLazy("convertSurrogateToName", "customIconSrc", "unicodeEmoji");
function getRoleIconSrc(role: Role) {
const icon = getRoleIconData(role, 20);
if (!icon) return;
const { customIconSrc, unicodeEmoji } = icon;
return customIconSrc ?? unicodeEmoji?.url;
}
function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, header }: { permissions: Array<RoleOrUserPermission>; guild: Guild; modalProps: ModalProps; header: string; }) {
@ -86,31 +88,34 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
size={ModalSize.LARGE}
>
<ModalHeader>
<Text className={cl("perms-title")} variant="heading-lg/semibold">{header} permissions:</Text>
<Text className={cl("modal-title")} variant="heading-lg/semibold">{header} permissions:</Text>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<ModalContent className={cl("modal-content")}>
{!selectedItem && (
<div className={cl("perms-no-perms")}>
<div className={cl("modal-no-perms")}>
<Text variant="heading-lg/normal">No permissions to display!</Text>
</div>
)}
{selectedItem && (
<div className={cl("perms-container")}>
<div className={cl("perms-list")}>
<div className={cl("modal-container")}>
<ScrollerThin className={cl("modal-list")} orientation="auto">
{permissions.map((permission, index) => {
const user = UserStore.getUser(permission.id ?? "");
const role = roles[permission.id ?? ""];
const user: User | undefined = UserStore.getUser(permission.id ?? "");
const role: Role | undefined = roles[permission.id ?? ""];
const roleIconSrc = role != null ? getRoleIconSrc(role) : undefined;
return (
<button
className={cl("perms-list-item-btn")}
<div
className={cl("modal-list-item-btn")}
onClick={() => selectItem(index)}
role="button"
tabIndex={0}
>
<div
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
className={cl("modal-list-item", { "modal-list-item-active": selectedItemIndex === index })}
onContextMenu={e => {
if (permission.type === PermissionType.Role)
ContextMenuApi.openContextMenu(e, () => (
@ -124,7 +129,6 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
ContextMenuApi.openContextMenu(e, () => (
<UserContextMenu
userId={permission.id!}
onClose={modalProps.onClose}
/>
));
}
@ -132,13 +136,19 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
>
{(permission.type === PermissionType.Role || permission.type === PermissionType.Owner) && (
<span
className={cl("perms-role-circle")}
className={cl("modal-role-circle")}
style={{ backgroundColor: role?.colorString ?? "var(--primary-300)" }}
/>
)}
{permission.type === PermissionType.User && user !== undefined && (
{permission.type === PermissionType.Role && roleIconSrc != null && (
<img
className={cl("perms-user-img")}
className={cl("modal-role-image")}
src={roleIconSrc}
/>
)}
{permission.type === PermissionType.User && user != null && (
<img
className={cl("modal-user-img")}
src={user.getAvatarURL(void 0, void 0, false)}
/>
)}
@ -147,28 +157,25 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
permission.type === PermissionType.Role
? role?.name ?? "Unknown Role"
: permission.type === PermissionType.User
? (user && getUniqueUsername(user)) ?? "Unknown User"
? (user != null && getUniqueUsername(user)) ?? "Unknown User"
: (
<Flex style={{ gap: "0.2em", justifyItems: "center" }}>
@owner
<OwnerCrownIcon
height={18}
width={18}
aria-hidden="true"
/>
<OwnerCrownIcon height={18} width={18} aria-hidden="true" />
</Flex>
)
}
</Text>
</div>
</button>
</div>
);
})}
</div>
<div className={cl("perms-perms")}>
</ScrollerThin>
<div className={cl("modal-divider")} />
<ScrollerThin className={cl("modal-perms")} orientation="auto">
{Object.entries(PermissionsBits).map(([permissionName, bit]) => (
<div className={cl("perms-perms-item")}>
<div className={cl("perms-perms-item-icon")}>
<div className={cl("modal-perms-item")}>
<div className={cl("modal-perms-item-icon")}>
{(() => {
const { permissions, overwriteAllow, overwriteDeny } = selectedItem;
@ -192,11 +199,11 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
</Tooltip>
</div>
))}
</div>
</ScrollerThin>
</div>
)}
</ModalContent>
</ModalRoot >
</ModalRoot>
);
}
@ -208,7 +215,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
aria-label="Role Options"
>
<Menu.MenuItem
id="vc-copy-role-id"
id={cl("copy-role-id")}
label={i18n.Messages.COPY_ID_ROLE}
action={() => {
Clipboard.copy(roleId);
@ -217,14 +224,13 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
{(settings.store as any).unsafeViewAsRole && (
<Menu.MenuItem
id="vc-pw-view-as-role"
id={cl("view-as-role")}
label={i18n.Messages.VIEW_AS_ROLE}
action={() => {
const role = GuildStore.getRole(guild.id, roleId);
if (!role) return;
onClose();
FluxDispatcher.dispatch({
type: "IMPERSONATE_UPDATE",
guildId: guild.id,
@ -235,15 +241,14 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
}
}
});
}
}
}}
/>
)}
</Menu.Menu>
);
}
function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) {
function UserContextMenu({ userId }: { userId: string; }) {
return (
<Menu.Menu
navId={cl("user-context-menu")}
@ -251,7 +256,7 @@ function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => v
aria-label="User Options"
>
<Menu.MenuItem
id="vc-copy-user-id"
id={cl("copy-user-id")}
label={i18n.Messages.COPY_ID_USER}
action={() => {
Clipboard.copy(userId);
@ -263,4 +268,13 @@ function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => v
const RolesAndUsersPermissions = ErrorBoundary.wrap(RolesAndUsersPermissionsComponent);
export default openRolesAndUsersPermissionsModal;
export default function openRolesAndUsersPermissionsModal(permissions: Array<RoleOrUserPermission>, guild: Guild, header: string) {
return openModal(modalProps => (
<RolesAndUsersPermissions
modalProps={modalProps}
permissions={permissions}
guild={guild}
header={header}
/>
));
}