+
No permissions to display!
)}
{selectedItem && (
-
-
+
+
{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 (
-
+
);
})}
-
-
+
+
+
{Object.entries(PermissionsBits).map(([permissionName, bit]) => (
-
-
+
+
{(() => {
const { permissions, overwriteAllow, overwriteDeny } = selectedItem;
@@ -192,11 +199,11 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
))}
-
+
)}
-
+
);
}
@@ -208,7 +215,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
aria-label="Role Options"
>
{
Clipboard.copy(roleId);
@@ -217,14 +224,13 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
{(settings.store as any).unsafeViewAsRole && (
{
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
}
}
});
- }
- }
+ }}
/>
)}
);
}
-function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) {
+function UserContextMenu({ userId }: { userId: string; }) {
return (
v
aria-label="User Options"
>
{
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, guild: Guild, header: string) {
+ return openModal(modalProps => (
+
+ ));
+}
diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx
index f53f93b6..7c0858f1 100644
--- a/src/plugins/permissionsViewer/components/UserPermissions.tsx
+++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx
@@ -29,6 +29,7 @@ import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermi
interface UserPermission {
permission: string;
+ roleName: string;
roleColor: string;
rolePosition: number;
}
@@ -37,7 +38,7 @@ type UserPermissions = Array;
const { RoleRootClasses, RoleClasses, RoleBorderClasses } = proxyLazyWebpack(() => {
const [RoleRootClasses, RoleClasses, RoleBorderClasses] = findBulk(
- filters.byProps("root", "showMoreButton", "collapseButton"),
+ filters.byProps("root", "expandButton", "collapseButton"),
filters.byProps("role", "roleCircle", "roleName"),
filters.byProps("roleCircle", "dot", "dotBorderColor")
) as Record[];
@@ -45,8 +46,48 @@ const { RoleRootClasses, RoleClasses, RoleBorderClasses } = proxyLazyWebpack(()
return { RoleRootClasses, RoleClasses, RoleBorderClasses };
});
+interface FakeRoleProps extends React.HTMLAttributes {
+ text: string;
+ color: string;
+}
+
+function FakeRole({ text, color, ...props }: FakeRoleProps) {
+ return (
+
+ );
+}
+
+interface GrantedByTooltipProps {
+ roleName: string;
+ roleColor: string;
+}
+
+function GrantedByTooltip({ roleName, roleColor }: GrantedByTooltipProps) {
+ return (
+ <>
+ Granted By
+
+ >
+ );
+}
+
function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { guild: Guild; guildMember: GuildMember; forceOpen?: boolean; }) {
- const stns = settings.use(["permissionsSortOrder"]);
+ const { permissionsSortOrder } = settings.use(["permissionsSortOrder"]);
const [rolePermissions, userPermissions] = useMemo(() => {
const userPermissions: UserPermissions = [];
@@ -67,6 +108,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g
const OWNER = i18n.Messages.GUILD_OWNER || "Server Owner";
userPermissions.push({
permission: OWNER,
+ roleName: "Owner",
roleColor: "var(--primary-300)",
rolePosition: Infinity
});
@@ -75,10 +117,11 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g
sortUserRoles(userRoles);
for (const [permission, bit] of Object.entries(PermissionsBits)) {
- for (const { permissions, colorString, position } of userRoles) {
+ for (const { permissions, colorString, position, name } of userRoles) {
if ((permissions & bit) === bit) {
userPermissions.push({
permission: getPermissionString(permission),
+ roleName: name,
roleColor: colorString || "var(--primary-300)",
rolePosition: position
});
@@ -91,7 +134,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g
userPermissions.sort((a, b) => b.rolePosition - a.rolePosition);
return [rolePermissions, userPermissions];
- }, [stns.permissionsSortOrder]);
+ }, [permissionsSortOrder]);
return (
settings.store.defaultPermissionsDropdownState = !state}
defaultState={settings.store.defaultPermissionsDropdownState}
buttons={[
- (
+
{tooltipProps => (
-
+
)}
- )
+
]}>
{userPermissions.length > 0 && (
- {userPermissions.map(({ permission, roleColor }) => (
-
-
-
-
-
-
- {permission}
-
-
-
+ {userPermissions.map(({ permission, roleColor, roleName }) => (
+
}
+ tooltipClassName={cl("granted-by-container")}
+ tooltipContentClassName={cl("granted-by-content")}
+ >
+ {tooltipProps => (
+
+ )}
+
))}
)}
diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx
index 5039d04f..ca28f845 100644
--- a/src/plugins/permissionsViewer/index.tsx
+++ b/src/plugins/permissionsViewer/index.tsx
@@ -26,7 +26,7 @@ import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
-import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common";
+import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common";
import type { Guild, GuildMember } from "discord-types/general";
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
@@ -54,12 +54,12 @@ export const settings = definePluginSettings({
options: [
{ label: "Highest Role", value: PermissionsSortOrder.HighestRole, default: true },
{ label: "Lowest Role", value: PermissionsSortOrder.LowestRole }
- ],
+ ]
},
defaultPermissionsDropdownState: {
description: "Whether the permissions dropdown on user popouts should be open by default",
type: OptionType.BOOLEAN,
- default: false,
+ default: false
}
});
@@ -73,14 +73,12 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
action={() => {
const guild = GuildStore.getGuild(guildId);
- let permissions: RoleOrUserPermission[];
- let header: string;
-
- switch (type) {
- case MenuItemParentType.User: {
+ const { permissions, header } = match(type)
+ .returnType<{ permissions: RoleOrUserPermission[], header: string; }>()
+ .with(MenuItemParentType.User, () => {
const member = GuildMemberStore.getMember(guildId, id!);
- permissions = getSortedRoles(guild, member)
+ const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member)
.map(role => ({
type: PermissionType.Role,
...role
@@ -93,37 +91,37 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
});
}
- header = member.nick ?? UserStore.getUser(member.userId).username;
-
- break;
- }
-
- case MenuItemParentType.Channel: {
+ return {
+ permissions,
+ header: member.nick ?? UserStore.getUser(member.userId).username
+ };
+ })
+ .with(MenuItemParentType.Channel, () => {
const channel = ChannelStore.getChannel(id!);
- permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({
+ const permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({
type: type as PermissionType,
id,
overwriteAllow: allow,
overwriteDeny: deny
})), guildId);
- header = channel.name;
-
- break;
- }
-
- default: {
- permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({
+ return {
+ permissions,
+ header: channel.name
+ };
+ })
+ .otherwise(() => {
+ const permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({
type: PermissionType.Role,
...role
}));
- header = guild.name;
-
- break;
- }
- }
+ return {
+ permissions,
+ header: guild.name
+ };
+ });
openRolesAndUsersPermissionsModal(permissions, guild, header);
}}
@@ -133,32 +131,34 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
return (children, props) => {
- if (!props) return;
- if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild)))
+ if (
+ !props ||
+ (type === MenuItemParentType.User && !props.user) ||
+ (type === MenuItemParentType.Guild && !props.guild) ||
+ (type === MenuItemParentType.Channel && (!props.channel || !props.guild))
+ ) {
return;
+ }
const group = findGroupChildrenByChildId(childId, children);
- const item = (() => {
- switch (type) {
- case MenuItemParentType.User:
- return MenuItem(props.guildId, props.user.id, type);
- case MenuItemParentType.Channel:
- return MenuItem(props.guild.id, props.channel.id, type);
- case MenuItemParentType.Guild:
- return MenuItem(props.guild.id);
- default:
- return null;
- }
- })();
+ const item = match(type)
+ .with(MenuItemParentType.User, () => MenuItem(props.guildId, props.user.id, type))
+ .with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, props.channel.id, type))
+ .with(MenuItemParentType.Guild, () => MenuItem(props.guild.id))
+ .otherwise(() => null);
+
if (item == null) return;
- if (group)
- group.push(item);
- else if (childId === "roles" && props.guildId)
- // "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID"
+ if (group) {
+ return group.push(item);
+ }
+
+ // "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID"
+ if (childId === "roles" && props.guildId) {
children.splice(-1, 0, {item});
+ }
};
}
@@ -172,7 +172,7 @@ export default definePlugin({
{
find: ".VIEW_ALL_ROLES,",
replacement: {
- match: /children:"\+"\.concat\(\i\.length-\i\.length\).{0,20}\}\),/,
+ match: /\.collapseButton,.+?}\)}\),/,
replace: "$&$self.ViewPermissionsButton(arguments[0]),"
}
}
diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css
index 0ef961e5..0123f86e 100644
--- a/src/plugins/permissionsViewer/styles.css
+++ b/src/plugins/permissionsViewer/styles.css
@@ -1,20 +1,6 @@
/* User Permissions Component */
-.vc-permviewer-userperms-title-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 10px;
- margin-bottom: 6px;
-}
-
-.vc-permviewer-userperms-btns-container {
- display: flex;
- align-items: center;
-}
-
-.vc-permviewer-userperms-sortorder-btn {
- all: unset;
+.vc-permviewer-user-sortorder-btn {
cursor: pointer;
display: flex;
align-items: center;
@@ -23,27 +9,17 @@
height: 24px;
}
-.vc-permviewer-userperms-permdetails-btn {
- all: unset;
- cursor: pointer;
- display: flex;
- align-items: center;
-}
-
-.vc-permviewer-userperms-toggleperms-btn {
- all: unset;
- cursor: pointer;
- display: flex;
- align-items: center;
-}
-
/* RolesAndUsersPermissions Component */
-.vc-permviewer-perms-title {
+.vc-permviewer-modal-content {
+ padding: 16px 4px 16px 16px;
+}
+
+.vc-permviewer-modal-title {
flex-grow: 1;
}
-.vc-permviewer-perms-no-perms {
+.vc-permviewer-modal-no-perms {
width: 100%;
height: 100%;
display: flex;
@@ -52,101 +28,103 @@
text-align: center;
}
-.vc-permviewer-perms-container {
- display: grid;
- grid-template-columns: 1fr 2fr;
- grid-template-areas: "list permissions";
- padding: 16px 0;
+.vc-permviewer-modal-container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ gap: 8px;
}
-.vc-permviewer-perms-list {
- grid-area: list;
+.vc-permviewer-modal-list {
display: flex;
flex-direction: column;
gap: 2px;
- border-right: 2px solid var(--background-modifier-active);
+ padding-right: 8px;
+ width: 200px;
}
-.vc-permviewer-perms-list-item-btn {
- all: unset;
+.vc-permviewer-modal-list-item-btn {
cursor: pointer;
}
-.vc-permviewer-perms-list-item {
+.vc-permviewer-modal-list-item {
display: flex;
align-items: center;
- padding: 8px 5px;
- cursor: pointer;
- width: 230px;
+ gap: 8px;
+ padding: 8px;
border-radius: 5px;
}
-.vc-permviewer-perms-list-item:hover {
+.vc-permviewer-modal-list-item:hover {
background-color: var(--background-modifier-hover);
}
-.vc-permviewer-perms-list-item-active {
+.vc-permviewer-modal-list-item-active {
background-color: var(--background-modifier-selected);
}
-.vc-permviewer-perms-list-item > div {
+.vc-permviewer-modal-list-item > div {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
-.vc-permviewer-perms-role-circle {
+.vc-permviewer-modal-role-circle {
border-radius: 50%;
width: 12px;
height: 12px;
- margin-left: 3px;
- margin-right: 11px;
flex-shrink: 0;
}
-.vc-permviewer-perms-user-img {
+.vc-permviewer-modal-role-image {
+ width: 20px;
+ height: 20px;
+ object-fit: contain;
+}
+
+.vc-permviewer-modal-user-img {
border-radius: 50%;
width: 20px;
height: 20px;
- margin-right: 6px;
}
-.vc-permviewer-perms-perms {
- grid-area: permissions;
+.vc-permviewer-modal-divider {
+ width: 2px;
+ background-color: var(--background-modifier-active);
+}
+
+.vc-permviewer-modal-perms {
display: flex;
flex-direction: column;
- margin-left: 5px;
+ padding-right: 8px;
}
-.vc-permviewer-perms-perms-item {
- position: relative;
+.vc-permviewer-modal-perms-item {
display: flex;
align-items: center;
- padding: 10px;
+ gap: 5px;
+ padding: 10px 2px 10px 10px;
border-bottom: 2px solid var(--background-modifier-active);
}
-.vc-permviewer-perms-perms-item:last-child {
+.vc-permviewer-modal-perms-item:last-child {
border: 0;
}
-.vc-permviewer-perms-perms-item-icon {
+.vc-permviewer-modal-perms-item-icon {
border: 1px solid var(--background-modifier-selected);
width: 24px;
height: 24px;
- margin-right: 5px;
}
-.vc-permviewer-perms-perms-item .vc-info-icon {
+.vc-permviewer-modal-perms-item .vc-info-icon {
color: var(--interactive-muted);
+ margin-left: auto;
cursor: pointer;
- position: absolute;
- right: 0;
- scale: 0.9;
transition: color ease-in 0.1s;
}
-.vc-permviewer-perms-perms-item .vc-info-icon:hover {
+.vc-permviewer-modal-perms-item .vc-info-icon:hover {
color: var(--interactive-active);
}
@@ -167,3 +145,14 @@
background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6));
border-color: var(--profile-body-border-color)
}
+
+.vc-permviewer-granted-by-container {
+ max-width: 300px;
+ width: auto;
+}
+
+.vc-permviewer-granted-by-content {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts
index b0c5bfe6..7dfa8cb4 100644
--- a/src/plugins/pronoundb/index.ts
+++ b/src/plugins/pronoundb/index.ts
@@ -57,7 +57,7 @@ export default definePlugin({
},
{
match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/,
- replace: '$&+vcHasPendingPronouns?"":` (${vcPronounSource})`'
+ replace: '$&+(vcHasPendingPronouns?"":` (${vcPronounSource})`)'
},
{
match: /(\.pronounsText.+?children:)(\i)/,
diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx
index e12a98ac..71ac021f 100644
--- a/src/plugins/reviewDB/components/ReviewModal.tsx
+++ b/src/plugins/reviewDB/components/ReviewModal.tsx
@@ -22,12 +22,13 @@ import { useForceUpdater } from "@utils/react";
import { Paginator, Text, useRef, useState } from "@webpack/common";
import { Auth } from "../auth";
+import { ReviewType } from "../entities";
import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
import { cl } from "../utils";
import ReviewComponent from "./ReviewComponent";
import ReviewsView, { ReviewsInputComponent } from "./ReviewsView";
-function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; modalKey: string, discordId: string; name: string; }) {
+function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: any; modalKey: string, discordId: string; name: string; type: ReviewType; }) {
const [data, setData] = useState();
const [signal, refetch] = useForceUpdater(true);
const [page, setPage] = useState(1);
@@ -58,6 +59,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod
onFetchReviews={setData}
scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })}
hideOwnReview
+ type={type}
/>
@@ -95,7 +97,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod
);
}
-export function openReviewsModal(discordId: string, name: string) {
+export function openReviewsModal(discordId: string, name: string, type: ReviewType) {
const modalKey = "vc-rdb-modal-" + Date.now();
openModal(props => (
@@ -104,6 +106,7 @@ export function openReviewsModal(discordId: string, name: string) {
modalProps={props}
discordId={discordId}
name={name}
+ type={type}
/>
), { modalKey });
}
diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx
index 76a0be47..7a7d8d02 100644
--- a/src/plugins/reviewDB/components/ReviewsView.tsx
+++ b/src/plugins/reviewDB/components/ReviewsView.tsx
@@ -21,7 +21,7 @@ import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpa
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
import { Auth, authorize } from "../auth";
-import { Review } from "../entities";
+import { Review, ReviewType } from "../entities";
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
import { settings } from "../settings";
import { cl, showToast } from "../utils";
@@ -45,6 +45,7 @@ interface Props extends UserProps {
page?: number;
scrollToTop?(): void;
hideOwnReview?: boolean;
+ type: ReviewType;
}
export default function ReviewsView({
@@ -56,6 +57,7 @@ export default function ReviewsView({
page = 1,
showInput = false,
hideOwnReview = false,
+ type,
}: Props) {
const [signal, refetch] = useForceUpdater(true);
@@ -80,6 +82,7 @@ export default function ReviewsView({
reviews={reviewData!.reviews}
hideOwnReview={hideOwnReview}
profileId={discordId}
+ type={type}
/>
{showInput && (
@@ -94,7 +97,7 @@ export default function ReviewsView({
);
}
-function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) {
+function ReviewList({ refetch, reviews, hideOwnReview, profileId, type }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; type: ReviewType; }) {
const myId = UserStore.getCurrentUser().id;
return (
@@ -111,7 +114,7 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch():
{reviews?.length === 0 && (
- Looks like nobody reviewed this user yet. You could be the first!
+ Looks like nobody reviewed this {type === ReviewType.User ? "user" : "server"} yet. You could be the first!
)}
diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx
index caf9bacb..1164a2c5 100644
--- a/src/plugins/reviewDB/index.tsx
+++ b/src/plugins/reviewDB/index.tsx
@@ -30,7 +30,7 @@ import { Guild, User } from "discord-types/general";
import { Auth, initAuth, updateAuth } from "./auth";
import { openReviewsModal } from "./components/ReviewModal";
-import { NotificationType } from "./entities";
+import { NotificationType, ReviewType } from "./entities";
import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
import { settings } from "./settings";
import { showToast } from "./utils";
@@ -44,7 +44,7 @@ const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { gu
label="View Reviews"
id="vc-rdb-server-reviews"
icon={OpenExternalIcon}
- action={() => openReviewsModal(guild.id, guild.name)}
+ action={() => openReviewsModal(guild.id, guild.name, ReviewType.Server)}
/>
);
};
@@ -56,7 +56,7 @@ const userContextPatch: NavContextMenuPatchCallback = (children, { user }: { use
label="View Reviews"
id="vc-rdb-user-reviews"
icon={OpenExternalIcon}
- action={() => openReviewsModal(user.id, user.username)}
+ action={() => openReviewsModal(user.id, user.username, ReviewType.User)}
/>
);
};
@@ -157,7 +157,7 @@ export default definePlugin({
return (