Wrap decorators in flex with gap to avoid adding margins

This commit is contained in:
Nuckyz 2025-02-02 17:59:22 -03:00
parent 6cccb54ffc
commit ae98cfb637
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
14 changed files with 91 additions and 55 deletions

View file

@ -43,20 +43,21 @@ interface DecoratorProps {
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null; export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
type OnlyIn = "guilds" | "dms"; type OnlyIn = "guilds" | "dms";
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>(); export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) { export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
decorators.set(identifier, { render, onlyIn }); decoratorsFactories.set(identifier, { render, onlyIn });
} }
export function removeMemberListDecorator(identifier: string) { export function removeMemberListDecorator(identifier: string) {
decorators.delete(identifier); decoratorsFactories.delete(identifier);
} }
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { export function __getDecorators(props: DecoratorProps): JSX.Element {
const isInGuild = !!(props.guildId); const isInGuild = !!(props.guildId);
return Array.from(
decorators.entries(), const decorators = Array.from(
decoratorsFactories.entries(),
([key, { render: Decorator, onlyIn }]) => { ([key, { render: Decorator, onlyIn }]) => {
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild)) if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
return null; return null;
@ -68,4 +69,10 @@ export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
); );
} }
); );
return (
<div className="vc-member-list-decorators-wrapper">
{decorators}
</div>
);
} }

View file

@ -48,23 +48,29 @@ export interface MessageDecorationProps {
} }
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null; export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
export const decorations = new Map<string, MessageDecorationFactory>(); export const decorationsFactories = new Map<string, MessageDecorationFactory>();
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) { export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
decorations.set(identifier, decoration); decorationsFactories.set(identifier, decoration);
} }
export function removeMessageDecoration(identifier: string) { export function removeMessageDecoration(identifier: string) {
decorations.delete(identifier); decorationsFactories.delete(identifier);
} }
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] { export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element {
return Array.from( const decorations = Array.from(
decorations.entries(), decorationsFactories.entries(),
([key, Decoration]) => ( ([key, Decoration]) => (
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}> <ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
<Decoration {...props} /> <Decoration {...props} />
</ErrorBoundary> </ErrorBoundary>
) )
); );
return (
<div className="vc-message-decorations-wrapper">
{decorations}
</div>
);
} }

View file

@ -19,10 +19,15 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import managedStyle from "./style.css?managed";
export default definePlugin({ export default definePlugin({
name: "MemberListDecoratorsAPI", name: "MemberListDecoratorsAPI",
description: "API to add decorators to member list (both in servers and DMs)", description: "API to add decorators to member list (both in servers and DMs)",
authors: [Devs.TheSun, Devs.Ven], authors: [Devs.TheSun, Devs.Ven],
managedStyle,
patches: [ patches: [
{ {
find: ".lostPermission)", find: ".lostPermission)",
@ -32,7 +37,7 @@ export default definePlugin({
replace: "$&vencordProps=$1," replace: "$&vencordProps=$1,"
}, { }, {
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/, match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
} }
] ]
}, },
@ -40,8 +45,8 @@ export default definePlugin({
find: "PrivateChannel.renderAvatar", find: "PrivateChannel.renderAvatar",
replacement: { replacement: {
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/, match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]" replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]"
} }
} }
], ]
}); });

View file

@ -0,0 +1,11 @@
.vc-member-list-decorators-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 0.25em;
}
.vc-member-list-decorators-wrapper:not(:empty) {
/* Margin to match default Discord decorators */
margin-left: 0.25em;
}

View file

@ -19,17 +19,22 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import managedStyle from "./style.css?managed";
export default definePlugin({ export default definePlugin({
name: "MessageDecorationsAPI", name: "MessageDecorationsAPI",
description: "API to add decorations to messages", description: "API to add decorations to messages",
authors: [Devs.TheSun], authors: [Devs.TheSun],
managedStyle,
patches: [ patches: [
{ {
find: '"Message Username"', find: '"Message Username"',
replacement: { replacement: {
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/, match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
} }
} }
], ]
}); });

View file

@ -0,0 +1,15 @@
.vc-message-decorations-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.25em;
}
.vc-message-decorations-wrapper:not(:empty) {
/* Margin to match default Discord decorators */
margin-left: 0.25em;
/* Align vertically */
position: relative;
top: 0.2rem;
}

View file

@ -17,14 +17,13 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { getUserSettingLazy } from "@api/UserSettings"; import { getUserSettingLazy } from "@api/UserSettings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import style from "./style.css?managed"; import managedStyle from "./style.css?managed";
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON"); const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
@ -90,6 +89,8 @@ export default definePlugin({
dependencies: ["UserSettingsAPI"], dependencies: ["UserSettingsAPI"],
settings, settings,
managedStyle,
patches: [ patches: [
{ {
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
@ -102,11 +103,4 @@ export default definePlugin({
GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }), GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }),
start() {
enableStyle(style);
},
stop() {
disableStyle(style);
}
}); });

View file

@ -18,7 +18,6 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -29,7 +28,7 @@ import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier"; import { Magnifier, MagnifierProps } from "./components/Magnifier";
import { ELEMENT_ID } from "./constants"; import { ELEMENT_ID } from "./constants";
import styles from "./styles.css?managed"; import managedStyle from "./styles.css?managed";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
saveZoomValues: { saveZoomValues: {
@ -160,6 +159,8 @@ export default definePlugin({
authors: [Devs.Aria], authors: [Devs.Aria],
tags: ["ImageUtilities"], tags: ["ImageUtilities"],
managedStyle,
patches: [ patches: [
{ {
find: ".contain,SCALE_DOWN:", find: ".contain,SCALE_DOWN:",
@ -252,14 +253,12 @@ export default definePlugin({
}, },
start() { start() {
enableStyle(styles);
this.element = document.createElement("div"); this.element = document.createElement("div");
this.element.classList.add("MagnifierContainer"); this.element.classList.add("MagnifierContainer");
document.body.appendChild(this.element); document.body.appendChild(this.element);
}, },
stop() { stop() {
disableStyle(styles);
// so componenetWillUnMount gets called if Magnifier component is still alive // so componenetWillUnMount gets called if Magnifier component is still alive
this.root && this.root.unmount(); this.root && this.root.unmount();
this.element?.remove(); this.element?.remove();

View file

@ -26,6 +26,7 @@ import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecor
import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover"; import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
import { Settings, SettingsStore } from "@api/Settings"; import { Settings, SettingsStore } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches"; import { canonicalizeFind } from "@utils/patches";
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
@ -254,7 +255,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { const {
name, commands, contextMenus, userProfileBadge, name, commands, contextMenus, managedStyle, userProfileBadge,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
} = p; } = p;
@ -298,6 +299,8 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
} }
} }
if (managedStyle) enableStyle(managedStyle);
if (userProfileBadge) addProfileBadge(userProfileBadge); if (userProfileBadge) addProfileBadge(userProfileBadge);
if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit); if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit);
@ -315,7 +318,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { const {
name, commands, contextMenus, userProfileBadge, name, commands, contextMenus, managedStyle, userProfileBadge,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
} = p; } = p;
@ -357,6 +360,8 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
} }
} }
if (managedStyle) disableStyle(managedStyle);
if (userProfileBadge) removeProfileBadge(userProfileBadge); if (userProfileBadge) removeProfileBadge(userProfileBadge);
if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit); if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit);

View file

@ -133,7 +133,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
})); }));
} }
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => {
if (!user || user.bot) return null; if (!user || user.bot) return null;
ensureOwnStatus(user); ensureOwnStatus(user);
@ -155,11 +155,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
return ( return (
<span <span
className="vc-platform-indicator" className="vc-platform-indicator"
style={{ style={{ gap: "2px" }}
marginLeft: wantMargin ? 4 : 0,
top: wantTopMargin ? 2 : 0,
gap: 2
}}
> >
{icons} {icons}
</span> </span>
@ -190,7 +186,7 @@ const indicatorLocations = {
description: "Inside messages", description: "Inside messages",
onEnable: () => addMessageDecoration("platform-indicator", props => onEnable: () => addMessageDecoration("platform-indicator", props =>
<ErrorBoundary noop> <ErrorBoundary noop>
<PlatformIndicator user={props.message?.author} wantTopMargin={true} /> <PlatformIndicator user={props.message?.author} />
</ErrorBoundary> </ErrorBoundary>
), ),
onDisable: () => removeMessageDecoration("platform-indicator") onDisable: () => removeMessageDecoration("platform-indicator")

View file

@ -130,15 +130,13 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
interface VoiceChannelIndicatorProps { interface VoiceChannelIndicatorProps {
userId: string; userId: string;
isMessageIndicator?: boolean;
isProfile?: boolean;
isActionButton?: boolean; isActionButton?: boolean;
shouldHighlight?: boolean; shouldHighlight?: boolean;
} }
const clickTimers = {} as Record<string, any>; const clickTimers = {} as Record<string, any>;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
@ -182,7 +180,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndi
{props => { {props => {
const iconProps: IconProps = { const iconProps: IconProps = {
...props, ...props,
className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight), className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
size: isActionButton ? 20 : undefined, size: isActionButton ? 20 : undefined,
onClick onClick
}; };

View file

@ -60,7 +60,7 @@ export default definePlugin({
find: "#{intl::USER_PROFILE_LOAD_ERROR}", find: "#{intl::USER_PROFILE_LOAD_ERROR}",
replacement: { replacement: {
match: /(\.fetchError.+?\?)null/, match: /(\.fetchError.+?\?)null/,
replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})` replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})`
}, },
predicate: () => settings.store.showInUserProfileModal predicate: () => settings.store.showInUserProfileModal
}, },
@ -99,7 +99,7 @@ export default definePlugin({
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />); addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
} }
if (settings.store.showInMessages) { if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />); addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} />);
} }
}, },

View file

@ -13,16 +13,6 @@
color: var(--interactive-hover); color: var(--interactive-hover);
} }
.vc-uvs-speaker-margin {
margin-left: 4px;
}
.vc-uvs-message-indicator {
display: inline-flex;
top: 2.5px;
position: relative;
}
.vc-uvs-tooltip-container { .vc-uvs-tooltip-container {
max-width: 300px; max-width: 300px;
} }

View file

@ -150,6 +150,11 @@ export interface PluginDef {
tags?: string[]; tags?: string[];
/**
* Managed style to automatically enable and disable when the plugin is enabled or disabled
*/
managedStyle?: string;
userProfileBadge?: ProfileBadge; userProfileBadge?: ProfileBadge;
onMessageClick?: MessageClickListener; onMessageClick?: MessageClickListener;