forked from Vendicated/Vencord
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(plugins): Permissions Viewer (Vendicated#477)
Co-authored-by: V <[email protected]>
- Loading branch information
1 parent
9c1b3a9
commit 64b3834
Showing
11 changed files
with
938 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
.vc-open-external-icon { | ||
transform: rotate(45deg); | ||
} | ||
|
||
.vc-owner-crown-icon { | ||
color: var(--text-warning); | ||
} |
225 changes: 225 additions & 0 deletions
225
src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
/* | ||
* Vencord, a modification for Discord's desktop app | ||
* Copyright (c) 2023 Vendicated and contributors | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import ErrorBoundary from "@components/ErrorBoundary"; | ||
import { Flex } from "@components/Flex"; | ||
import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; | ||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; | ||
import { ContextMenu, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; | ||
import type { Guild } from "discord-types/general"; | ||
|
||
import { cl, getPermissionDescription, getPermissionString } from "../utils"; | ||
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons"; | ||
|
||
export const enum PermissionType { | ||
Role = 0, | ||
User = 1, | ||
Owner = 2 | ||
} | ||
|
||
export interface RoleOrUserPermission { | ||
type: PermissionType; | ||
id?: string; | ||
permissions?: bigint; | ||
overwriteAllow?: bigint; | ||
overwriteDeny?: bigint; | ||
} | ||
|
||
function openRolesAndUsersPermissionsModal(permissions: Array<RoleOrUserPermission>, guild: Guild, header: string) { | ||
return openModal(modalProps => ( | ||
<RolesAndUsersPermissions | ||
modalProps={modalProps} | ||
permissions={permissions} | ||
guild={guild} | ||
header={header} | ||
/> | ||
)); | ||
} | ||
|
||
function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, header }: { permissions: Array<RoleOrUserPermission>; guild: Guild; modalProps: ModalProps; header: string; }) { | ||
permissions.sort((a, b) => a.type - b.type); | ||
|
||
useStateFromStores( | ||
[GuildMemberStore], | ||
() => GuildMemberStore.getMemberIds(guild.id), | ||
null, | ||
(old, current) => old.length === current.length | ||
); | ||
|
||
useEffect(() => { | ||
const usersToRequest = permissions | ||
.filter(p => p.type === PermissionType.User && !GuildMemberStore.isMember(guild.id, p.id!)) | ||
.map(({ id }) => id); | ||
|
||
FluxDispatcher.dispatch({ | ||
type: "GUILD_MEMBERS_REQUEST", | ||
guildIds: [guild.id], | ||
userIds: usersToRequest | ||
}); | ||
}, []); | ||
|
||
const [selectedItemIndex, selectItem] = useState(0); | ||
const selectedItem = permissions[selectedItemIndex]; | ||
|
||
return ( | ||
<ModalRoot | ||
{...modalProps} | ||
size={ModalSize.LARGE} | ||
> | ||
<ModalHeader> | ||
<Text className={cl("perms-title")} variant="heading-lg/semibold">{header} permissions:</Text> | ||
<ModalCloseButton onClick={modalProps.onClose} /> | ||
</ModalHeader> | ||
|
||
<ModalContent> | ||
{!selectedItem && ( | ||
<div className={cl("perms-no-perms")}> | ||
<Text variant="heading-lg/normal">No permissions to display!</Text> | ||
</div> | ||
)} | ||
|
||
{selectedItem && ( | ||
<div className={cl("perms-container")}> | ||
<div className={cl("perms-list")}> | ||
{permissions.map((permission, index) => { | ||
const user = UserStore.getUser(permission.id ?? ""); | ||
const role = guild.roles[permission.id ?? ""]; | ||
|
||
return ( | ||
<button | ||
className={cl("perms-list-item-btn")} | ||
onClick={() => selectItem(index)} | ||
> | ||
<div | ||
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })} | ||
onContextMenu={e => { | ||
if (permission.type === PermissionType.Role) | ||
ContextMenu.open(e, () => ( | ||
<RoleContextMenu | ||
guild={guild} | ||
roleId={permission.id!} | ||
onClose={modalProps.onClose} | ||
/> | ||
)); | ||
}} | ||
> | ||
{(permission.type === PermissionType.Role || permission.type === PermissionType.Owner) && ( | ||
<span | ||
className={cl("perms-role-circle")} | ||
style={{ backgroundColor: role?.colorString ?? "var(--primary-300)" }} | ||
/> | ||
)} | ||
{permission.type === PermissionType.User && user !== undefined && ( | ||
<img | ||
className={cl("perms-user-img")} | ||
src={user.getAvatarURL(void 0, void 0, false)} | ||
/> | ||
)} | ||
<Text variant="text-md/normal"> | ||
{ | ||
permission.type === PermissionType.Role | ||
? role?.name || "Unknown Role" | ||
: permission.type === PermissionType.User | ||
? user?.tag || "Unknown User" | ||
: ( | ||
<Flex style={{ gap: "0.2em", justifyItems: "center" }}> | ||
@owner | ||
<OwnerCrownIcon | ||
height={18} | ||
width={18} | ||
aria-hidden="true" | ||
/> | ||
</Flex> | ||
) | ||
} | ||
</Text> | ||
</div> | ||
</button> | ||
); | ||
})} | ||
</div> | ||
<div className={cl("perms-perms")}> | ||
{Object.entries(PermissionsBits).map(([permissionName, bit]) => ( | ||
<div className={cl("perms-perms-item")}> | ||
<div className={cl("perms-perms-item-icon")}> | ||
{(() => { | ||
const { permissions, overwriteAllow, overwriteDeny } = selectedItem; | ||
|
||
if (permissions) | ||
return (permissions & bit) === bit | ||
? PermissionAllowedIcon() | ||
: PermissionDeniedIcon(); | ||
|
||
if (overwriteAllow && (overwriteAllow & bit) === bit) | ||
return PermissionAllowedIcon(); | ||
if (overwriteDeny && (overwriteDeny & bit) === bit) | ||
return PermissionDeniedIcon(); | ||
|
||
return PermissionDefaultIcon(); | ||
})()} | ||
</div> | ||
<Text variant="text-md/normal">{getPermissionString(permissionName)}</Text> | ||
|
||
<Tooltip text={getPermissionDescription(permissionName) || "No Description"}> | ||
{props => <InfoIcon {...props} />} | ||
</Tooltip> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
)} | ||
</ModalContent> | ||
</ModalRoot > | ||
); | ||
} | ||
|
||
function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: string; onClose: () => void; }) { | ||
return ( | ||
<Menu.Menu | ||
navId={cl("role-context-menu")} | ||
onClose={ContextMenu.close} | ||
aria-label="Role Options" | ||
> | ||
<Menu.MenuItem | ||
id="vc-pw-view-as-role" | ||
label="View As Role" | ||
action={() => { | ||
const role = guild.roles[roleId]; | ||
if (!role) return; | ||
|
||
onClose(); | ||
|
||
FluxDispatcher.dispatch({ | ||
type: "IMPERSONATE_UPDATE", | ||
guildId: guild.id, | ||
data: { | ||
type: "ROLES", | ||
roles: { | ||
[roleId]: role | ||
} | ||
} | ||
}); | ||
}} | ||
/> | ||
</Menu.Menu> | ||
); | ||
} | ||
|
||
const RolesAndUsersPermissions = ErrorBoundary.wrap(RolesAndUsersPermissionsComponent); | ||
|
||
export default openRolesAndUsersPermissionsModal; |
Oops, something went wrong.