PermissionsViewer: add to simplified profiles
This commit is contained in:
parent
705da29df5
commit
6d4c9339dc
7 changed files with 140 additions and 6 deletions
|
@ -31,10 +31,20 @@ export interface ExpandableHeaderProps {
|
||||||
headerText: string;
|
headerText: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
buttons?: React.ReactNode[];
|
buttons?: React.ReactNode[];
|
||||||
|
forceOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) {
|
export function ExpandableHeader({
|
||||||
const [showContent, setShowContent] = useState(defaultState);
|
children,
|
||||||
|
onMoreClick,
|
||||||
|
buttons,
|
||||||
|
moreTooltipText,
|
||||||
|
onDropDownClick,
|
||||||
|
headerText,
|
||||||
|
defaultState = false,
|
||||||
|
forceOpen = false,
|
||||||
|
}: ExpandableHeaderProps) {
|
||||||
|
const [showContent, setShowContent] = useState(defaultState || forceOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -90,6 +100,7 @@ export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipTe
|
||||||
setShowContent(v => !v);
|
setShowContent(v => !v);
|
||||||
onDropDownClick?.(showContent);
|
onDropDownClick?.(showContent);
|
||||||
}}
|
}}
|
||||||
|
disabled={forceOpen}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
|
|
|
@ -290,3 +290,21 @@ export function NoEntrySignIcon(props: IconProps) {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SafetyIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-safety-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M4.27 5.22A2.66 2.66 0 0 0 3 7.5v2.3c0 5.6 3.3 10.68 8.42 12.95.37.17.79.17 1.16 0A14.18 14.18 0 0 0 21 9.78V7.5c0-.93-.48-1.78-1.27-2.27l-6.17-3.76a3 3 0 0 0-3.12 0L4.27 5.22ZM6 7.68l6-3.66V12H6.22C6.08 11.28 6 10.54 6 9.78v-2.1Zm6 12.01V12h5.78A11.19 11.19 0 0 1 12 19.7Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ const Classes = proxyLazyWebpack(() =>
|
||||||
))
|
))
|
||||||
) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>;
|
) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>;
|
||||||
|
|
||||||
function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) {
|
function UserPermissionsComponent({ guild, guildMember, showBorder, forceOpen = false }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; forceOpen?: boolean; }) {
|
||||||
const stns = settings.use(["permissionsSortOrder"]);
|
const stns = settings.use(["permissionsSortOrder"]);
|
||||||
|
|
||||||
const [rolePermissions, userPermissions] = useMemo(() => {
|
const [rolePermissions, userPermissions] = useMemo(() => {
|
||||||
|
@ -95,6 +95,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpandableHeader
|
<ExpandableHeader
|
||||||
|
forceOpen={forceOpen}
|
||||||
headerText="Permissions"
|
headerText="Permissions"
|
||||||
moreTooltipText="Role Details"
|
moreTooltipText="Role Details"
|
||||||
onMoreClick={() =>
|
onMoreClick={() =>
|
||||||
|
|
|
@ -20,15 +20,22 @@ import "./styles.css";
|
||||||
|
|
||||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { SafetyIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common";
|
||||||
import type { Guild, GuildMember } from "discord-types/general";
|
import type { Guild, GuildMember } from "discord-types/general";
|
||||||
|
|
||||||
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
|
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
|
||||||
import UserPermissions from "./components/UserPermissions";
|
import UserPermissions from "./components/UserPermissions";
|
||||||
import { getSortedRoles, sortPermissionOverwrites } from "./utils";
|
import { getSortedRoles, sortPermissionOverwrites } from "./utils";
|
||||||
|
|
||||||
|
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
||||||
|
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
|
||||||
|
|
||||||
export const enum PermissionsSortOrder {
|
export const enum PermissionsSortOrder {
|
||||||
HighestRole,
|
HighestRole,
|
||||||
LowestRole
|
LowestRole
|
||||||
|
@ -168,10 +175,45 @@ export default definePlugin({
|
||||||
match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
||||||
replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),`
|
replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".VIEW_ALL_ROLES,",
|
||||||
|
replacement: {
|
||||||
|
match: /children:"\+"\.concat\(\i\.length-\i\.length\).{0,20}\}\),/,
|
||||||
|
replace: "$&$self.ViewPermissionsButton(arguments[0]),"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
|
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBorder: boolean) =>
|
||||||
|
!!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBorder} />,
|
||||||
|
|
||||||
|
ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => (
|
||||||
|
<Popout
|
||||||
|
position="bottom"
|
||||||
|
align="center"
|
||||||
|
renderPopout={() => (
|
||||||
|
<Dialog className={PopoutClasses.container} style={{ width: "500px" }}>
|
||||||
|
<UserPermissions guild={guild} guildMember={guildMember} showBorder forceOpen />
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{popoutProps => (
|
||||||
|
<TooltipContainer text="View Permissions">
|
||||||
|
<Button
|
||||||
|
{...popoutProps}
|
||||||
|
color={Button.Colors.CUSTOM}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
size={Button.Sizes.NONE}
|
||||||
|
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.icon)}
|
||||||
|
className={classes(RoleButtonClasses.button, RoleButtonClasses.icon, "vc-permviewer-role-button")}
|
||||||
|
>
|
||||||
|
<SafetyIcon height="16" width="16" />
|
||||||
|
</Button>
|
||||||
|
</TooltipContainer>
|
||||||
|
)}
|
||||||
|
</Popout>
|
||||||
|
), { noop: true }),
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
|
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
|
||||||
|
|
|
@ -149,3 +149,21 @@
|
||||||
.vc-permviewer-perms-perms-item .vc-info-icon:hover {
|
.vc-permviewer-perms-perms-item .vc-info-icon:hover {
|
||||||
color: var(--interactive-active);
|
color: var(--interactive-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* copy pasted from discord cause impossible to webpack find */
|
||||||
|
.vc-permviewer-role-button {
|
||||||
|
border-radius: var(--radius-xs);
|
||||||
|
background: var(--bg-mod-faint);
|
||||||
|
color: var(--interactive-normal);
|
||||||
|
border: 1px solid var(--border-faint);
|
||||||
|
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||||
|
width: -moz-fit-content;
|
||||||
|
width: fit-content;
|
||||||
|
height: 24px;
|
||||||
|
padding: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-profile-theme .vc-permviewer-role-button {
|
||||||
|
background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6));
|
||||||
|
border-color: var(--profile-body-border-color)
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export let Card: t.Card;
|
||||||
export let Button: t.Button;
|
export let Button: t.Button;
|
||||||
export let Switch: t.Switch;
|
export let Switch: t.Switch;
|
||||||
export let Tooltip: t.Tooltip;
|
export let Tooltip: t.Tooltip;
|
||||||
|
export let TooltipContainer: t.TooltipContainer;
|
||||||
export let TextInput: t.TextInput;
|
export let TextInput: t.TextInput;
|
||||||
export let TextArea: t.TextArea;
|
export let TextArea: t.TextArea;
|
||||||
export let Text: t.Text;
|
export let Text: t.Text;
|
||||||
|
@ -66,6 +67,7 @@ waitFor(["FormItem", "Button"], m => {
|
||||||
Button,
|
Button,
|
||||||
FormSwitch: Switch,
|
FormSwitch: Switch,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
TooltipContainer,
|
||||||
TextInput,
|
TextInput,
|
||||||
TextArea,
|
TextArea,
|
||||||
Text,
|
Text,
|
||||||
|
|
44
src/webpack/common/types/components.d.ts
vendored
44
src/webpack/common/types/components.d.ts
vendored
|
@ -101,6 +101,28 @@ export type Tooltip = ComponentType<{
|
||||||
|
|
||||||
export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>;
|
export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>;
|
||||||
|
|
||||||
|
export type TooltipContainer = ComponentType<PropsWithChildren<{
|
||||||
|
text: ReactNode;
|
||||||
|
element?: "div" | "span";
|
||||||
|
"aria-label"?: string | false;
|
||||||
|
|
||||||
|
delay?: number;
|
||||||
|
/** Tooltip.Colors.BLACK */
|
||||||
|
color?: string;
|
||||||
|
/** TooltipPositions.TOP */
|
||||||
|
position?: string;
|
||||||
|
spacing?: number;
|
||||||
|
|
||||||
|
className?: string;
|
||||||
|
tooltipClassName?: string | null;
|
||||||
|
tooltipContentClassName?: string | null;
|
||||||
|
|
||||||
|
allowOverflow?: boolean;
|
||||||
|
forceOpen?: boolean;
|
||||||
|
hideOnClick?: boolean;
|
||||||
|
disableTooltipPointerEvents?: boolean;
|
||||||
|
}>>;
|
||||||
|
|
||||||
export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
outline?: boolean;
|
outline?: boolean;
|
||||||
|
@ -110,6 +132,26 @@ export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
||||||
Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>;
|
Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ComboboxPopout = ComponentType<PropsWithChildren<{
|
||||||
|
value: Set<any>;
|
||||||
|
placeholder: string;
|
||||||
|
children(query: string): ReactNode[];
|
||||||
|
|
||||||
|
onChange(value: any): void;
|
||||||
|
itemToString?: (item: any) => string;
|
||||||
|
onClose?(): void;
|
||||||
|
|
||||||
|
className?: string;
|
||||||
|
listClassName?: string;
|
||||||
|
|
||||||
|
|
||||||
|
autoFocus?: boolean;
|
||||||
|
multiSelect?: boolean;
|
||||||
|
maxVisibleItems?: number;
|
||||||
|
showScrollbar?: boolean;
|
||||||
|
|
||||||
|
}>>;
|
||||||
|
|
||||||
export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & {
|
export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & {
|
||||||
/** Button.Looks.FILLED */
|
/** Button.Looks.FILLED */
|
||||||
look?: string;
|
look?: string;
|
||||||
|
@ -375,7 +417,7 @@ export type Popout = ComponentType<{
|
||||||
Animation: typeof PopoutAnimation;
|
Animation: typeof PopoutAnimation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Dialog = ComponentType<PropsWithChildren<any>>;
|
export type Dialog = ComponentType<JSX.IntrinsicElements["div"]>;
|
||||||
|
|
||||||
type Resolve = (data: { theme: "light" | "dark", saturation: number; }) => {
|
type Resolve = (data: { theme: "light" | "dark", saturation: number; }) => {
|
||||||
hex(): string;
|
hex(): string;
|
||||||
|
|
Loading…
Reference in a new issue