diff --git a/.github/workflows/uploadBuilds.yml b/.github/workflows/uploadBuilds.yml index 989db697..24f53e7b 100644 --- a/.github/workflows/uploadBuilds.yml +++ b/.github/workflows/uploadBuilds.yml @@ -1,7 +1,7 @@ name: Upload Builds on: workflow_run: - workflows: [Release] + workflows: [Release, Test] types: [completed] branches: [main, dev] env: diff --git a/.github/workflows/uploadPlugins.yml b/.github/workflows/uploadPlugins.yml index 8edd474f..27d8efdb 100644 --- a/.github/workflows/uploadPlugins.yml +++ b/.github/workflows/uploadPlugins.yml @@ -1,7 +1,7 @@ name: Upload Plugins JSONs on: workflow_run: - workflows: [Release] + workflows: [Release, Test] types: [completed] branches: [main] env: diff --git a/src/equicordplugins/imagePreview/index.ts b/src/equicordplugins/imagePreview/index.ts index 4f77ab6b..486eb737 100644 --- a/src/equicordplugins/imagePreview/index.ts +++ b/src/equicordplugins/imagePreview/index.ts @@ -263,8 +263,10 @@ function loadImagePreview(url: string, sticker: boolean) { fileSize.appendChild(showingSize); } - preview.appendChild(fileName); - preview.appendChild(fileInfo); + if (settings.store.fileInformation) { + preview.appendChild(fileName); + preview.appendChild(fileInfo); + } }); if (loadingSpinner) loadingSpinner.remove(); @@ -287,7 +289,7 @@ function loadImagePreview(url: string, sticker: boolean) { } }); - currentPreview.addEventListener("mouseout", () => { + currentPreviewFile.addEventListener("mouseout", () => { if (currentPreview && !isCtrlHeld && shouldKeepPreviewOpen) { deleteCurrentPreview(); shouldKeepPreviewOpen = false; diff --git a/src/equicordplugins/imagePreview/settings.ts b/src/equicordplugins/imagePreview/settings.ts index d17c1ac0..46b865bb 100644 --- a/src/equicordplugins/imagePreview/settings.ts +++ b/src/equicordplugins/imagePreview/settings.ts @@ -43,6 +43,11 @@ const settings = definePluginSettings({ description: "Fixes the image preview to the initial point of hover", default: false }, + fileInformation: { + type: OptionType.BOOLEAN, + description: "Show file information on hover", + default: true + }, hoverDelay: { type: OptionType.SLIDER, description: "Delay in seconds before the image preview appears", diff --git a/src/equicordplugins/imagePreview/styles.css b/src/equicordplugins/imagePreview/styles.css index d513ec0a..4167d0a3 100644 --- a/src/equicordplugins/imagePreview/styles.css +++ b/src/equicordplugins/imagePreview/styles.css @@ -107,4 +107,4 @@ max-height: 90vh; object-fit: contain; display: none; -} \ No newline at end of file +} diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index a0d138cd..50b043b6 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -15,6 +15,8 @@ import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; +import { settings } from "."; + const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const FriendRow = findComponentByCodeLazy(".listName,discriminatorClass"); @@ -31,7 +33,8 @@ export function openGuildInfoModal(guild: Guild) { const enum Tabs { ServerInfo, Friends, - BlockedUsers + BlockedUsers, + MutualMembers } interface GuildProps { @@ -56,6 +59,7 @@ function renderTimestamp(timestamp: number) { function GuildInfoModal({ guild }: GuildProps) { const [friendCount, setFriendCount] = useState(); const [blockedCount, setBlockedCount] = useState(); + const [mutualMembersCount, setMutualMembersCount] = useState(); useEffect(() => { fetched.friends = false; @@ -126,6 +130,12 @@ function GuildInfoModal({ guild }: GuildProps) { > Friends{friendCount !== undefined ? ` (${friendCount})` : ""} + + Mutual Server Members{mutualMembersCount !== undefined ? ` (${mutualMembersCount})` : ""} + {currentTab === Tabs.ServerInfo && } {currentTab === Tabs.Friends && } + {currentTab === Tabs.MutualMembers && } {currentTab === Tabs.BlockedUsers && } @@ -243,16 +254,152 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC useEffect(() => setCount(members.length), [members.length]); + const sortedMembers = members + .map(id => UserStore.getUser(id)) + .sort( + (a, b) => { + switch (settings.store.sorting) { + case "username": + return a.username.localeCompare(b.username); + case "displayname": + return a?.globalName?.localeCompare(b?.globalName || b.username) + || a.username.localeCompare(b?.globalName || b.username); + default: + return 0; + } + } + ); + + return ( - {members.map(id => + {sortedMembers.map(user => ( openUserProfile(id)} + user={user} + status={PresenceStore.getStatus(user.id) || "offline"} + onSelect={() => openUserProfile(user.id)} onContextMenu={() => { }} /> - )} + ))} + + ); +} + +interface MemberWithMutuals { + id: string; + mutualCount: number; + mutualGuilds: Array<{ + guild: Guild; + iconUrl: string | null; + }>; +} + +function getMutualGuilds(id: string): MemberWithMutuals { + const mutualGuilds: Array<{ guild: Guild; iconUrl: string | null; }> = []; + + for (const guild of Object.values(GuildStore.getGuilds())) { + if (GuildMemberStore.isMember(guild.id, id)) { + const iconUrl = guild.icon + ? IconUtils.getGuildIconURL({ + id: guild.id, + icon: guild.icon, + canAnimate: true, + size: 20 + }) ?? null + : null; + + mutualGuilds.push({ guild, iconUrl }); + } + } + + return { + id, + mutualCount: mutualGuilds.length, + mutualGuilds + }; +} + +function MutualServerIcons({ member }: { member: MemberWithMutuals; }) { + const MAX_ICONS = 3; + const { mutualGuilds, mutualCount } = member; + + return ( +
+ {mutualGuilds.slice(0, MAX_ICONS).map(({ guild, iconUrl }) => ( +
+ {iconUrl ? ( + + ) : ( +
{guild.acronym}
+ )} +
+ ))} + {mutualCount > MAX_ICONS && ( +
+ +{mutualCount - MAX_ICONS} +
+ )} +
+ ); +} + +function MutualMembersTab({ guild, setCount }: RelationshipProps) { + const [members, setMembers] = useState([]); + const currentUserId = UserStore.getCurrentUser().id; + + useEffect(() => { + const guildMembers = GuildMemberStore.getMemberIds(guild.id); + const membersWithMutuals = guildMembers + .map(id => getMutualGuilds(id)) + // dont show yourself and members that are only in this server + .filter(member => member.mutualCount > 1 && member.id !== currentUserId); + + // sort by mutual server count (descending) + membersWithMutuals.sort((a, b) => b.mutualCount - a.mutualCount); + + setMembers(membersWithMutuals); + setCount(membersWithMutuals.length); + }, [guild.id]); + + return ( + + {members + .map(member => { + const user = UserStore.getUser(member.id); + return { ...member, user }; + }) + .filter(Boolean) + .sort((a, b) => { + switch (settings.store.sorting) { + case "username": + return a.user.username.localeCompare(b.user.username); + case "displayname": + return a.user?.globalName?.localeCompare(b.user?.globalName || b.user.username) + || a.user.username.localeCompare(b.user?.globalName || b.user.username); + default: + return 0; + } + }) + .map(member => ( +
openUserProfile(member.id)} + > +
+ { }} + onContextMenu={() => { }} + mutualGuilds={member.mutualCount} + /> +
+
e.stopPropagation()}> + +
+
+ ))}
); } diff --git a/src/plugins/serverInfo/index.tsx b/src/plugins/serverInfo/index.tsx index df87a5f2..aa20c859 100644 --- a/src/plugins/serverInfo/index.tsx +++ b/src/plugins/serverInfo/index.tsx @@ -5,9 +5,9 @@ */ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { migratePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import { definePluginSettings, migratePluginSettings } from "@api/Settings"; +import { Devs, EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; import { Menu } from "@webpack/common"; import { Guild } from "discord-types/general"; @@ -25,15 +25,38 @@ const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; ); }; +export const settings = definePluginSettings({ + sorting: { + type: OptionType.SELECT, + description: "Username or if applicable Display Name", + options: [ + { + label: "Username", + value: "username" + }, + { + label: "Display Name", + value: "displayname", + default: true + }, + { + label: "Dont Sort", + value: "none", + } + ] + } +}); + migratePluginSettings("ServerInfo", "ServerProfile"); // what was I thinking with this name lmao export default definePlugin({ name: "ServerInfo", description: "Allows you to view info about a server", - authors: [Devs.Ven, Devs.Nuckyz], + authors: [Devs.Ven, Devs.Nuckyz, EquicordDevs.Z1xus], dependencies: ["DynamicImageModalAPI"], tags: ["guild", "info", "ServerProfile"], contextMenus: { "guild-context": Patch, "guild-header-popout": Patch - } + }, + settings }); diff --git a/src/plugins/serverInfo/styles.css b/src/plugins/serverInfo/styles.css index 8c88e4f4..815e99f3 100644 --- a/src/plugins/serverInfo/styles.css +++ b/src/plugins/serverInfo/styles.css @@ -51,6 +51,7 @@ color: var(--interactive-normal); cursor: pointer; height: 39px; + margin-right: -10px; line-height: 14px; } @@ -93,6 +94,74 @@ max-height: 500px; } +.vc-gp-member-row { + display: flex; + align-items: center; + padding-right: 16px; + cursor: pointer; + position: relative; + margin: 1px 0; + width: 100%; +} + +.vc-gp-member-row:hover { + background-color: var(--background-modifier-hover); +} + +.vc-gp-member-content { + flex: 1; + min-width: 0; + width: 100%; + position: relative; +} + +.vc-gp-member-icons { + user-select: none; + position: absolute; + right: 20px; + z-index: 2; +} + +.vc-gp-mutual-guilds { + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; + padding-left: 8px; +} + +.vc-gp-guild-icon { + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--background-tertiary); + overflow: hidden; +} + +.vc-gp-guild-icon img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.vc-gp-guild-acronym { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 8px; + font-weight: 600; + color: var(--text-normal); + text-transform: uppercase; +} + +.vc-gp-guild-count { + font-size: 12px; + color: var(--text-muted); + font-weight: 600; +} + .vc-gp-scroller [class^="listRow"] { margin: 1px 0; } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2dabaae0..7f279231 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -580,6 +580,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "RamziAH", id: 1279957227612147747n }, + SomeAspy: { + name: "SomeAspy", + id: 516750892372852754n + }, } satisfies Record); export const EquicordDevs = Object.freeze({ @@ -936,6 +940,10 @@ export const EquicordDevs = Object.freeze({ name: "nvhhr", id: 165098921071345666n }, + Z1xus: { + name: "Z1xus", + id: 377450600797044746n, + } } satisfies Record); // iife so #__PURE__ works correctly