From cd517f225ce5f1fedab5bd147fb4eccccde5ac1d Mon Sep 17 00:00:00 2001 From: sadan <117494111+sadan4@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:27:15 -0400 Subject: [PATCH 1/2] make feature v2 --- packages/discord-types/src/utils.d.ts | 17 ++++++ .../tabs/plugins/components/SliderSetting.tsx | 15 ++++-- src/plugins/showConnections/index.tsx | 53 ++++++++++++++++--- src/utils/discord.tsx | 7 +-- src/utils/types.ts | 6 +++ src/webpack/common/utils.ts | 2 +- 6 files changed, 87 insertions(+), 13 deletions(-) diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 77b6f88b8bb..5c37aced7c4 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -333,3 +333,20 @@ export interface CommandOptions { max_value?: number; autocomplete?: boolean; } + +export type OpenUserProfileModalProps = { + userId: string; + guildId: string | null | undefined; + showGuildProfile?: boolean; + channelId: string; + analyticsLocation: { + page: string; + section: string; + }; + section?: "USER_INFO" | "BOT_INFO" | "ACTIVITY" | "MUTUAL_GUILDS" | "MUTUAL_FRIENDS" | "BOT_DATA_ACCESS"; + subsection?: "ROLES" | "CONNECTIONS" | "NOTE" | "RECENT_ACTIVITY"; +}; + +export interface UserProfileActions { + openUserProfileModal(props: OpenUserProfileModalProps): Promise; +} diff --git a/src/components/settings/tabs/plugins/components/SliderSetting.tsx b/src/components/settings/tabs/plugins/components/SliderSetting.tsx index 85a52da2935..3d009946a31 100644 --- a/src/components/settings/tabs/plugins/components/SliderSetting.tsx +++ b/src/components/settings/tabs/plugins/components/SliderSetting.tsx @@ -26,13 +26,20 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC const [error, setError] = useState(null); + function getValue(value: number): number { + if (option.onlyInts) { + return Math.round(value); + } + return value; + } + function handleChange(newValue: number): void { - const isValid = option.isValid?.call(definedSettings, newValue) ?? true; + const isValid = option.isValid?.call(definedSettings, getValue(newValue)) ?? true; setError(resolveError(isValid)); if (isValid === true) { - onChange(newValue); + onChange(getValue(newValue)); } } @@ -44,7 +51,9 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC maxValue={option.markers[option.markers.length - 1]} initialValue={def} onValueChange={handleChange} - onValueRender={(v: number) => String(v.toFixed(2))} + keyboardStep={option.onlyInts ? 1 : undefined} + onValueRender={(v: number) => String(option.onlyInts ? Math.round(v) : v.toFixed(2))} + onMarkerRender={(v: number) => option.markers.includes(v) ? String(getValue(v)) : null} stickToMarkers={option.stickToMarkers ?? true} disabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index c0080a68e05..09974376020 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -23,11 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; +import { openUserProfile } from "@utils/discord"; import { copyWithToast } from "@utils/misc"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; import { ConnectedAccount, User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; -import { Tooltip, UserProfileStore } from "@webpack/common"; +import { Clickable, Tooltip, UserProfileStore } from "@webpack/common"; import OpenInAppPlugin from "plugins/openInApp"; import { VerifiedIcon } from "./VerifiedIcon"; @@ -58,6 +59,14 @@ const settings = definePluginSettings({ { label: "Cozy", value: Spacing.COZY }, // US Spelling :/ { label: "Roomy", value: Spacing.ROOMY } ] + }, + maxNumberOfConnections: { + type: OptionType.SLIDER, + description: "Max number of connections to show", + markers: makeRange(6, 48, 7), + default: 13, + stickToMarkers: false, + onlyInts: true, } }); @@ -82,20 +91,52 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { if (!profile) return null; - const connections = profile.connectedAccounts; - if (!connections?.length) + const { connectedAccounts } = profile; + if (!connectedAccounts?.length) return null; + const connections = connectedAccounts.map(connection => ); + + if (connectedAccounts.length > settings.store.maxNumberOfConnections) { + connections.length = settings.store.maxNumberOfConnections; + connections.push( openUserProfile(id, { + section: "USER_INFO", + subsection: "CONNECTIONS" + })} + />); + } + return ( - {connections.map(connection => )} + {connections} ); } + +function ConnectionsMoreIcon({ onClick }: { onClick: () => void; }) { + return ( + + {props => ( + + {/* discords icon refuses to work with a custom width/height for some reason, so we use our own SVG */} + + + + + )} + + ); +} + function CompactConnectionComponent({ connection, theme }: { connection: ConnectedAccount, theme: string; }) { const platform = platforms.get(useLegacyPlatformType(connection.type)); const url = platform.getPlatformUserUrl?.(connection); @@ -157,7 +198,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect export default definePlugin({ name: "ShowConnections", description: "Show connected accounts in user popouts", - authors: [Devs.TheKodeToad], + authors: [Devs.TheKodeToad, Devs.sadan], settings, patches: [ diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 837136256fb..a3422ef5419 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,7 +17,7 @@ */ import { MessageObject } from "@api/MessageEvents"; -import { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; +import { Channel, CloudUpload, Guild, GuildFeatures, Message, OpenUserProfileModalProps, User } from "@vencord/discord-types"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Except } from "type-fest"; @@ -164,7 +164,7 @@ export function openImageModal(item: Except, mediaModalP }); } -export async function openUserProfile(id: string) { +export async function openUserProfile(id: string, extraOptions: Partial = {}) { const user = await UserUtils.getUser(id); if (!user) throw new Error("No such user: " + id); @@ -176,7 +176,8 @@ export async function openUserProfile(id: string) { analyticsLocation: { page: guildId ? "Guild Channel" : "DM Channel", section: "Profile Popout" - } + }, + ...extraOptions, }); } diff --git a/src/utils/types.ts b/src/utils/types.ts index 2f1d6396c47..1a2e6d44f27 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -313,6 +313,12 @@ export interface PluginSettingSliderDef { * If false, allow users to select values in-between your markers. */ stickToMarkers?: boolean; + /** + * If true, only allow integers to be selected. + * + * If {@link stickToMarkers} is enabled this will have no effect + */ + onlyInts?: boolean; } export interface IPluginOptionComponentProps { diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 633eceea515..ee1855118ce 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -172,7 +172,7 @@ export const { zustandPersist } = mapMangledModuleLazy(".onRehydrateStorage)?", export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); -export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); +export const UserProfileActions: t.UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const InviteActions = findByPropsLazy("resolveInvite"); export const ChannelActionCreators = findByPropsLazy("openPrivateChannel"); From 69b3a2b60e603f9dc9a6fa9d92aa1cc7576b9a4e Mon Sep 17 00:00:00 2001 From: sadan <117494111+sadan4@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:31:16 -0400 Subject: [PATCH 2/2] prefer interface over type --- packages/discord-types/src/utils.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 5c37aced7c4..793e688f62e 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -334,7 +334,7 @@ export interface CommandOptions { autocomplete?: boolean; } -export type OpenUserProfileModalProps = { +export interface OpenUserProfileModalProps { userId: string; guildId: string | null | undefined; showGuildProfile?: boolean; @@ -345,7 +345,7 @@ export type OpenUserProfileModalProps = { }; section?: "USER_INFO" | "BOT_INFO" | "ACTIVITY" | "MUTUAL_GUILDS" | "MUTUAL_FRIENDS" | "BOT_DATA_ACCESS"; subsection?: "ROLES" | "CONNECTIONS" | "NOTE" | "RECENT_ACTIVITY"; -}; +} export interface UserProfileActions { openUserProfileModal(props: OpenUserProfileModalProps): Promise;