diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 247557e3c..e88f1e2b9 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -334,5 +334,6 @@ export const commonRendererPlugins = [ banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), + banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), ...commonOpts.plugins ]; diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index bb9d37894..f718ab11f 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -77,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { Validator This section will tell you whether your themes can successfully be loaded
- {themeLinks.map(link => ( - { + const { label, link } = (() => { + const match = /^@(light|dark) (.*)/.exec(rawLink); + if (!match) return { label: rawLink, link: rawLink }; + + const [, mode, link] = match; + return { label: `[${mode} mode only] ${link}`, link }; + })(); + + return - {link} + {label} - - ))} + ; + })}
); @@ -296,6 +304,7 @@ function ThemesTab() { Paste links to css files here One link per line + You can prefix lines with @light or @dark to toggle them based on your Discord theme Make sure to use direct links to files (raw or github.io)! diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 358bae017..59f3d5fe2 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { Button, Forms, useStateFromStores } from "@webpack/common"; +import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); @@ -36,7 +36,6 @@ function setTheme(theme: string) { saveClientTheme({ theme }); } -const ThemeStore = findStoreLazy("ThemeStore"); const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); function ThemeSettings() { diff --git a/src/plugins/CopyFileContents/README.md b/src/plugins/copyFileContents/README.md similarity index 100% rename from src/plugins/CopyFileContents/README.md rename to src/plugins/copyFileContents/README.md diff --git a/src/plugins/CopyFileContents/index.tsx b/src/plugins/copyFileContents/index.tsx similarity index 100% rename from src/plugins/CopyFileContents/index.tsx rename to src/plugins/copyFileContents/index.tsx diff --git a/src/plugins/CopyFileContents/style.css b/src/plugins/copyFileContents/style.css similarity index 100% rename from src/plugins/CopyFileContents/style.css rename to src/plugins/copyFileContents/style.css diff --git a/src/plugins/ignoreActivities/README.md b/src/plugins/ignoreActivities/README.md new file mode 100644 index 000000000..abc75720f --- /dev/null +++ b/src/plugins/ignoreActivities/README.md @@ -0,0 +1,13 @@ +# IgnoreActivities + +Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings. + +![](https://github.com/user-attachments/assets/f0c19060-0ecf-4f1c-8165-a5aa40143c82) + +![](https://github.com/user-attachments/assets/73c3fa7a-5b90-41ee-a4d6-91fa76458b74) + +![](https://github.com/user-attachments/assets/1ab3fe73-3911-48d1-8a08-e976af614b41) + +The activity stays showing as a detected game even if ignored, differently from the stock Toggle Detection button from Discord: + +![](https://github.com/user-attachments/assets/08ea60c3-3a31-42de-ae4c-7535fbf1b45a) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 9e6c21bdd..02261b5ba 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -237,7 +237,7 @@ function isActivityTypeIgnored(type: number, id?: string) { export default definePlugin({ name: "IgnoreActivities", authors: [Devs.Nuckyz, Devs.Kylie], - description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", + description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below", dependencies: ["UserSettingsAPI"], settings, @@ -266,6 +266,7 @@ export default definePlugin({ replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` } }, + // Discord has 3 different components for activities. Currently, the last is the one being used { find: ".activityTitleText,variant", replacement: { @@ -279,6 +280,13 @@ export default definePlugin({ match: /\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),/, replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` } + }, + { + find: ".promotedLabelWrapperNonBanner,children", + replacement: { + match: /\.appDetailsHeaderContainer.+?children:\i.*?}\),(?<=application:(\i).+?)/, + replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` + } } ], diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index a1e73cabf..ec52b4061 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -20,7 +20,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; @@ -28,6 +28,7 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); +const ExpandableList = findComponentByCodeLazy(".mutualFriendItem]"); const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon"); function getGroupDMName(channel: Channel) { @@ -50,6 +51,29 @@ function getMutualGDMCountText(user: User) { return `${count === 0 ? "No" : count} Mutual Group${count !== 1 ? "s" : ""}`; } +function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { + return mutualDms.map(c => ( + { + onClose(); + SelectedChannelActionCreators.selectPrivateChannel(c.id); + }} + > + + +
+
{getGroupDMName(c)}
+
{c.recipients.length + 1} Members
+
+
+ )); +} + const IS_PATCHED = Symbol("MutualGroupDMs.Patched"); export default definePlugin({ @@ -70,6 +94,13 @@ export default definePlugin({ replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&" } ] + }, + { + find: 'section:"MUTUAL_FRIENDS"', + replacement: { + match: /\.openUserProfileModal.+?\)}\)}\)(?<=(\(0,\i\.jsxs?\)\(\i\.\i,{className:(\i)\.divider}\)).+?)/, + replace: "$&,$self.renderDMPageList({user: arguments[0].user, Divider: $1, listStyle: $2.list})" + } } ], @@ -84,28 +115,9 @@ export default definePlugin({ }, renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { - const mutualDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); + const mutualGDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); - const entries = mutualDms.map(c => ( - { - onClose(); - SelectedChannelActionCreators.selectPrivateChannel(c.id); - }} - > - - -
-
{getGroupDMName(c)}
-
{c.recipients.length + 1} Members
-
-
- )); + const entries = renderClickableGDMs(mutualGDms, onClose); return ( ); + }), + + renderDMPageList: ErrorBoundary.wrap(({ user, Divider, listStyle }: { user: User, Divider: JSX.Element, listStyle: string; }) => { + const mutualGDms = getMutualGroupDms(user.id); + if (mutualGDms.length === 0) return null; + + const header = getMutualGDMCountText(user); + + return ( + <> + {Divider} + { })} + /> + + ); }) }); diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 963750fa3..82925d610 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -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, guild: Guild, header: string) { - return openModal(modalProps => ( - - )); +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; guild: Guild; modalProps: ModalProps; header: string; }) { @@ -86,31 +88,34 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea size={ModalSize.LARGE} > - {header} permissions: + {header} permissions: - + {!selectedItem && ( -
+
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 dc2aa6fa4..7c0858f10 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; } @@ -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 ( +
+
+ +
+
+ + {text} + +
+
+ ); +} + +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 7c3967a37..ca28f845f 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}); + } }; } diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index 0ef961e5a..0123f86e2 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/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts index 3ab47b197..02a955a80 100644 --- a/src/plugins/volumeBooster/index.ts +++ b/src/plugins/volumeBooster/index.ts @@ -69,7 +69,7 @@ export default definePlugin({ // Patches needed for web/vesktop { find: "streamSourceNode", - predicate: () => IS_WEB, + predicate: () => !IS_DISCORD_DESKTOP, group: true, replacement: [ // Remove rounding algorithm diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts index 99f06004c..6a18948d1 100644 --- a/src/utils/quickCss.ts +++ b/src/utils/quickCss.ts @@ -17,6 +17,7 @@ */ import { Settings, SettingsStore } from "@api/Settings"; +import { ThemeStore } from "@webpack/common"; let style: HTMLStyleElement; @@ -59,7 +60,18 @@ async function initThemes() { const { themeLinks, enabledThemes } = Settings; - const links: string[] = [...themeLinks]; + // "darker" and "midnight" both count as dark + const activeTheme = ThemeStore.theme === "light" ? "light" : "dark"; + + const links = themeLinks + .map(rawLink => { + const match = /^@(light|dark) (.*)/.exec(rawLink); + if (!match) return rawLink; + + const [, mode, link] = match; + return mode === activeTheme ? link : null; + }) + .filter(link => link !== null); if (IS_WEB) { for (const theme of enabledThemes) { @@ -85,6 +97,7 @@ document.addEventListener("DOMContentLoaded", () => { SettingsStore.addChangeListener("themeLinks", initThemes); SettingsStore.addChangeListener("enabledThemes", initThemes); + ThemeStore.addChangeListener(initThemes); if (!IS_WEB) VencordNative.quickCss.addThemeChangeListener(initThemes); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 74813357a..8579f8b92 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -53,6 +53,7 @@ export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & { }; export let EmojiStore: t.EmojiStore; +export let ThemeStore: t.ThemeStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; @@ -84,3 +85,4 @@ waitForStore("GuildChannelStore", m => GuildChannelStore = m); waitForStore("MessageStore", m => MessageStore = m); waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); +waitForStore("ThemeStore", m => ThemeStore = m); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 5dcc95194..0588d5e4c 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -459,7 +459,7 @@ export type ScrollerThin = ComponentType>; } +export class ThemeStore extends FluxStore { + theme: "light" | "dark" | "darker" | "midnight"; + darkSidebar: boolean; + isSystemThemeAvailable: boolean; + systemPrefersColorScheme: "light" | "dark"; + systemTheme: null; +} + export type useStateFromStores = ( stores: t.FluxStore[], mapper: () => T,